😁 作者简介:一名大四的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:前端js专栏
👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气
🔥前言:
这里是关于js中Class继承的使用和原理,其中有关原型链的部分非常好。 这是我自己的学习JavaScript的笔记,希望可以帮助到大家,欢迎大家的补充和纠正
文章目录
第20章 Class的继承
20.1 简介
Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承更加清晰和方便
class Point{
}
class ColorPoint extends Point {
}
上述代码中定义了一个ColorPoint类,该类通过extend关键字继承了Point类的所有属性和方法
ES5的继承实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this
❗️ 注意点:
- 子类必须在constructor方法中调用super方法,否则新建实例时会报错,这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象
20.2 Object.getPrototypeOf()
这个方法可以用来从子类上获取父类,所以可以使用这个方法来判断一个类是否继承另一个类
20.3 super关键字
super这个关键字即可以当作函数使用,也可以当作对象使用,在这两种情况下,它的用法完全不同
第一种情况,super作为函数调用时代表父类的构造函数,ES6要求,子类的构造函数必须执行super函数
class A{}
class B extend A{
constructor(){
super()
}
}
📝 使用细节:
-
super虽然代表了父类的构造函数,但是返回的是子类的B的实例,即super内部的this指的是B,因为super()在这里相当于A.prototype.constructor.call(this)
-
super()是可以有参数的,参数代表父类构造函数的参数
-
作为函数,super()只能用在子类的构造函数之中,用在其他地方会报错
第二种情况,super作为对象时在普通方法中指向父类的原型对象,在静态方法中指向父类
class A{
p(){
return 2
}
}
class B extends A{
constructor(){
super();
console.log(super.p())
}
}
let b=new B()
上面的代码中,子类B中的super.p()就是将super当作一个对象来使用,这时,super在普通方法之中指向A.prototype,所以super.p()就相当于A.prototype.p()
❗️ 使用细节:
-
由于super指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super调用的
-
通过super调用父类的方法时,super会绑定子类的this,因此如果通过super对某一个属性赋值,这时super就是this赋值的属性会变成子类实例的属性
class A{ constructor(){ this.x=1 } } class B extends A{ constructor(){ super(); this.x=2 super.x=3 console.log(super.x) //undefined console.log(this.x) //3 } } let b=new B()
-
使用super时候,必须显示指定是作为函数还是担心使用,否则报错
20.4 类的prototypr属性和 __ proto __属性
在大多数浏览器的ES5实现之中,每一个对象都有 __ proto__ 属性,指向对应的构造函数的prototype属性,Class作为构造函数的语法糖,同时有prototype属性和 __ proto __ 属性,因此同时存在两条继承链
- 子类的 __ proto __ 属性表示构造函数的继承,总是指向父类
- 子类prototype属性的 __ proto__ 属性表示方法的继承,总是指向父类的prototype属性
class A{
}
class B extends A{
}
B.__proto__===A //true
B.prototype.__proto__ === A.prototype //true
造成这样的结果是因为类的继承是按照下面的模式实现的
class A{
}
class B{
}
//B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype)
//B的实例继承A的静态属性
Object.setPrototype(B,A)
const b=new B()
//Object。setPrototypeOf方法的实现
Object.setPrototypeOf=function(obj,proto){
obj.__proto__ =proto;
return obj
}
根据setPrototypeOf的实现方法和设计模式才造成了下面的结果
B.prototype.__proto__ = A.prototype
B.__proto__=A
**这两条继承链可以这样理解:作为一个对象,子类(B)的原型( __ proto __)是父类(A);作为一个构造函数,子类(B)的原型(prototype属性)是父类的实例。 **
20.4.1 extends的继承目标
extends关键字后面可以跟多种类型的值
下面我们来讨论3中特殊情况:
-
第一种情况,子类继承Object类
class A extends Object{ } A.__proto__ === Object //true A.prototype.__proto__===Object.prototype //true
这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例
-
第二种情况,不存在任何继承
class A{ } A.__proto__ === Function.prototype //true A.prototype.__proto__===Object.prototype //true
这种情况下,A作为一个基类就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象,所以A.prototype.__proto指向构造函数的prototype属性
-
第三种情况,子类继承null
class A extends null{ } A.__proto__ === Function.prototype //true A.prototype.__proto__===undefined //true
这种情况下,A也是一个普通函数,但是A调用后返回的对象不继承任何方法,所以它的__ proto __指向Function.prototype,实际上执行了下面的代码
class C extends null{ constructor(){return Object.create(null)} }
20.4.2 实例的 __ proto __属性
子类实例的 __ proto __ 属性的 __ proto __ 属性指向父类实例的 __ proto __属性。也就是说,子类的原型的原型是父类的原型
因此,可以通过子类实例的__ proto __ . __ proto __属性修改父类实例的行为
20.5 原生构造函数的继承
- Boolean()
- Number()
- String()
- Array()
- Data()
- Function()
- RegExp()
- Error()
- OBject()
以前,这些原生构造函数是无法继承的,比如,不能自己定义一个Array的子类
之所以会发生这种情况,是因为子类无法获得原生构造函数的内部属性,通过Array.apply()或者分配给原型对象都不行,原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性
现在 ES6可以自定义原生数据结构(比如 Array,String等)的子类,这就是ES5无法做到的
extends关键字不仅可以用来继承类,还可以用来继承原生的构造函数,因此可以在原生数据结构的基础上定义自己的数据结构,以下代码定义一个带版本功能的数组
class VersioneArray extends Array{
constructor(){
super()
this.history=[[]]
}
commit(){
this.history.push(this.slice())
}
revert(){
this.splice(0,this.length,this.history[this.history.length-1])
}
}
var x= new VersionedArray()
x.push(1)
x.push(2)
x//[1,2]
x.history //[[]]
❗️ 注意:继承Object的子类有一个行为差异,
class NewOBj extends OBject{
constructor(){
super(...arguments)
}
}
var o=new NewObj({attr:true})
o.attr ===true
上面的代码中,NewObj继承了Object,但是无法通过super方法向fuelingObject传参,这是因为ES6改变了Object构造函数的行为,一旦发现Object方法不是通过new Object() 这种形式调用,ES6规定Object构造函数忽略参数