JavaScript Class类 | 类的继承 - 类的使用 -原型与原型链
文章目录
JavaScript class类
基础概念
- 每次
new
一个实例,constructor
方法就会调用一次。 - 在函数定义时,会自动往函数添加
prototype
属性,默认是一个空Object
对象,这个对象叫做显式原型对象。 - 实例对象的
__proto__
属性,叫做隐式原型属性/隐式原型,实例化对象时自动添加的,实例对象的隐式原型=构造函数的显式原型 - 原型对象有一个
constructor
属性,指向函数对象 class
可以看作构造函数的一个语法糖class Person{ constructor(){} } console.log(typeof Person);//function console.log(Person===Person.prototype.constructor);//true
每个类都必须有一个
constructor
,如果没有显式声明,js 引擎会自动给它添加一个空的构造函数。
属性与方法相关概念
概念 | 定义位置 | 使用 | 特点 |
---|---|---|---|
原型方法 | constructor 外面 | 实例调用 | 定义在构造函数(类)的显式原型prototype 上 |
实例方法 | constructor 里面 | 实例调用 | 每new 一次,调用一次constructor ,constructor 内部会重新定义实例方法(改变this 指到实例),所以实例方法在每个实例上,方法同名但不是同一个。 |
静态方法 | static 标识符 修饰 | 类来调用 | 方法里的this 指向类本身,静态方法可以被子类继承 |
私有属性/方法 | # 标识符 修饰 | 只能在类的内部访问的方法和属性,外部不能访问 | 私有指的对类私有 |
ES5相关概念的写法
function A(x){}
A.prototype.show = function(){} // 原型方法
// 实例方法
function A(x){
this.x = x; // 实例属性
this.show = function(){} // 实例方法
}
私有字段
私有字段包括私有实例字段和私有静态字段
说明
1.私有字段#名称
(hash),访问时也需要携带#
。
2.私有字段在构造器或调用子类的 super()
方法时被添加到类的实例中。
// 可以通过super方法
class ClassWithPrivateField {
#privateField;
constructor() {
this.#privateField = 42; // 添加到类实例中
}
}
class SubClass extends ClassWithPrivateField {
#subPrivateField;
constructor() {
super(); // 添加到子类的实例中
this.#subPrivateField = 23;
}
}
new SubClass();
// SubClass {#privateField: 42, #subPrivateField: 23}
类的name属性 返回类的名字
class Person {
}
Person.name // Person
类的访问器方法
通过getter
、setter
访问器函数,可以对读写进行拦截操作
class Person {
constructor(name) {
this._name = name
}
// 类的访问器方法
get name() {
return this._name
}
set name(val) {
this._name = val
}
}
super关键字
规定:super()
之前不能访问 this
有一种说法解释: 因为构造器是用来对实例初始化的,而子类实例在初始化之前要先初始化它的父类成分。那接下来为什么 JS 语言不可以隐式调用 super,而要交给开发者?因为 JS 是弱类型的,super 调用时的传参没法自动预判,没法代劳。
super()
:在子类构造器中,把super
当作一个函数来调用,创建对象并让其执行父类的构造器方法super.property
和super.method():
通过super
访问超类的原型属性和方法
new的过程中发生了什么
- 创建一个空对象,这个空对象就是返回的实例
- 类内部的
this
指向这个空对象 - 实例的隐式原型
__proto__
指向构造函数的显式原型prototype
- 执行构造器函数,为实例添加方法或属性
- 获取构造器函数执行的结果,如果构造器函数有返回对象,则将其返回。如果没有返回创建的实例。
function myNew(Fn,...args){
let obj = {}; //1
let obj.__proto__ = Fn.prototype;//2
let result = Fn.apply(obj,args);//3
return result instanceof Object ? result : obj;//4
}
extends继承 重写-重载
作用:用于创建一个类的子类
说明:父类的.prototype
必须是一个 Object
或者 null
重写:同名属性或方法,同名方法就可以了,不需要参数个数
语法细节
类声明与类表达式
- 类的声明的特点
- 将类的名称添加到当前作用域中
- 结尾的大括号不需要加分号
- 类似
let
和const
的作用域提升规则
- 类的表达式的特点
- 不会将类的名称添加到当前作用域中,赋值的结果是一个函数(类的构造函数)
//类的声明
class Class1{}
//类的表达式
let Color = class{};
//类的匿名表达式
let C = class Color2{};
conselo.log(Color2); //Color2 is not defined
console.log(typeof C)//"function"
补充理解:let和const的作用域提升规则
var
声明的变量会使声明被提升到顶部,let
和const
也存在变量提升,只是提升的方式不同。
var
变量提升:变量的声明提升到顶部,值为undefined
let
、const
变量提升: 变量声明提升到顶部,只不过将该变量标记为尚未初始化
//原代码
function fn(){
console.log(answer); //undefined
var answer=42;
}
//变量提升
function fn(){
var answer;//声明提前
console.log(answer); // 值为undefined
answer=42;
}
let、const的暂时性死区
let
和 const
存在暂时性死区,代码执行过程中的一段时间内,在此期间无法使用标识符,也不能引用外层作用域的变量。
原因是也将声明提升到了顶部,只不过是标记该变量为尚未初始化
let answer;
function fn(){
//如果此时没有将变量变量提升到这里,answer应该取外层answer的值
// 提升到了这里并标记未尚未初始化
console.log(answer); //Uncaught ReferenceError: Cannot access 'answer' before initialization
let answer=42;
}
//理解暂时性死区是暂时的与时间相关
function temporalExample(){
const f = ()=>{
console.log(value)//这里不会报错
}
let value = 42;
f(); //调用时,value已经声明
}
类的继承
继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。
原型与隐式原型链
原型
函数在定义时会自动添加prototype属性显式原型属性,默认指向一个空Object对象,该对象称为显式原型对象。显式原型对象有一个constructor
属性,指向构造函数。
实例在创建对象时会自动添加__proto__
隐式原型属性,对象隐式原型的值=对应构造函数的显式原型的值
隐式原型链的概念
访问一个对象的属性时
1.现在自身属性中查找,找到返回
2.没有找到,再沿__proto__
这条链上找,找到返回
3.最终没找到,返回undefined
原型链的尽头是Object.prototype.__proto__ === null
原型链的作用
1.实现继承
2.数据共享,节约内存空间
特殊原型链
核心:实例的隐式原型指向构造函数显式原型
Function
可以看成是构造函数,也可以看成Function
的实例
Function.__proto__ === Function.prototype
Object
可以看成构造函数,也可以看成Function
的实例
Object.__ptoto__ === Function.prototype
原型链继承 - 子类的显式原型是父类的实例
本质:子类的显式原型是父类的实例
function Parent(){
this.name = 'parent'
this.play = [1,2,3]
}
function Child(){
this.name = 'child';
}
Child.prototype = new Parent();//执行Parant构造器
Parent.prototype.id = '1';
let child1 = new Child(); //执行Child
console.log(child1.name)//child
console.log(child1.id)//1
let child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[2,2,3]
原型链:子类实例child
自身找 -> 子类的__proto__
找(这里是父类的实例) -> 子类__proto__
的__proto__
找(父类的显式原型)
- 优点
- 共享父类的实例属性/方法和原型上的属性和方法
- 缺点
- 父类的引用属性(
play
)会被所有子类共享,其中一个子类修改,其他子类也会受到影响 - 子类的实例不能给父类型构造函数传参(
Child.prototype = new Parent()这里已经调用了父类构造器方法
,全程只调用了一次所以没办法实例化子类的时候动态传参)
- 父类的引用属性(
构造函数继承 - 子类构造函数中调用父类构造函数
本质:子类构造函数中调用父类构造函数 - 只会继承构造器中的东西
function Parent(name){
this.name = name
this.play = [1,2,3]
}
function Child(name){
Parent.call(this,name);//执行Parent函数 - 子类的每个实例都会将父类中的属性复制一份。
/*
this.name = name
this.play = [1,2,3]
*/
}
Parent.prototype.id = '1'
var child1 = new Child('child'); //---ES5和ES6的区别 先创造子类的实例,执行构造器函数调用 Parent.call(this),继承父类实例的方法
console.log(child1.name)//child
console.log(child1.id)//undefined
var child2 = new Child();
child1.play[0] = 2;
console.log(child2.play)//[1,2,3]
Parent.call(this,name) 第一次参数是指定函数Parent里的this 为参数this,name为Parent函数传递的参数
- 优点
- 可以在子类构造器中给父类构造函数传参
- 父类的引用对象不会共享
- 缺点
- 子类访问不了父类显式原型上的方法(从图里看子类和父类比较独立,只能继承构造器里的东西)
- 子类的实例每实例化一次,父类的构造器都会被调用一次
组合继承 原型链继承+构造函数继承
- 继承实例属性:子类的构造函数里调用父类的构造函数,防止父类引用类型被修改
- 继承原型上的属性和方法: 将父类的实例作为子类的原型,访问父类原型。
function Parent(name){
this.name = name
this.play = [1,2,3]
}
function Child(name){
//子类的每个实例都会将父类中的属性复制一份,访问时优先访问自己作用域中的
Parent.call(this,name); //调用一次父类构造器
}
//继承原型上的属性和方法
Child.prototype = new Parent();//调用一次父类构造器
Child.prototype.constructor = Child;
这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性
- 优点
- 父类的方法可以复用
- 可以在子类构造器中向父类构造器传参
- 父类构造器中的引用属性不会被共享
- 缺点
- 调用了两次父类构造器,原型链上会存在两份相同的属性和方法(
child
实例有,child
实例的隐式原型上也有)
- 调用了两次父类构造器,原型链上会存在两份相同的属性和方法(
寄生组合继承
实例原型链:子类的实例可以拥有父类的方法,通过Son.prototype.__proto__ = Father.prototype
构造器原型链:子类可以拥有父类的静态方法,通过Son.__proto__=Father
/*
1.子类继承父类的属性
*/
function child(name){
Person.call(this,name)
}
/*
2.子类可以看见父类的方法
先创建父类的实例
*/
// Object.create创建一个新对象,对象的隐式原型指向参数
Child.prototype = Object.create(Parent.prototype);
//Object.create创建的是一个新对象,所以需要显式指定constructor属性
Child.prototype.constructor = Child;
/*
3.子类可以看见父类的静态方法
*/
Child.__proto__ = Parant;
补充:Object.create原理
作用:创建一个新对象,新对象的隐式原型指向参数
function create(proto) {
function F(){}
F.prototype = proto
return new F()
}
案例
let obj = Object.create({name: 'johan'})
extends 继承原理 (同寄生组合继承)
核心代码
var Child = function (_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
// Object.getPrototypeOf(Child) 获取child的隐式原型
// Parent.call(this, name, age)
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Child).call(this, name, age));
_this.name = name;
_this.age = age;
return _this;
}
return Child;
}(Parent);
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call
&& (typeof call === "object" || typeof call === "function") ? call : self;
}
_inherits
的逻辑与寄生组合继承相同
- 子类可以继承父类原型空间的属性与方法
- 子类可以继承父类的静态属性和方法
function _inherits(subClass, superClass) {
// 如果有一个不是函数,则抛出报错
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
// 将 subClass.prototype 设置为 superClass.prototype 的实例
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
// Object.setPrototypeOf(obj, ) 指定对象obj的隐式原型为参数2 ,设置subClass的隐式原型为superClass
if (superClass)
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!