JS语言理解04 继承的总结
继承相关知识还是有点乱,来总结一下。
本质上,JS里面的继承都是通过原型链实现的(除了实例属性之外),原型链继承的关键就是对象的__proto__
属性,它应该指向另外一个对象的prototype
构造函数的继承
ES5里面最常用的继承方法是以下两种:
- 在子类内部执行父类(
call
/apply
改变this
),继承实例属性 - 子类的
prototype
等于父类的一个实例(new 父类
),继承实例属性和原型属性
ES6里面使用class
和extends
实现的继承
下面具体来看(下面提到的属性
,除非特殊说明,都是泛指属性和方法)
继承实例属性
在子类的内部让父类调用call
或者apply
的方法,这样只能继承父类的实例属性,不能继承原型上的属性。
1 | function Child() { |
继承原型属性
(1)直接让子类的原型等于父类的原型:
1 | Child.prototype = Father.prototype; |
(2)利用Object.setPrototypeOf
方法
使用Object.setPrototypeOf
方法来让指定Child.prototype.__proto__
等于Father.prototype
,实现了原型属性的继承(相当于new
过程的一部分)
1 | Object.setPrototypeOf(Child.prototype, Father.prototype); |
(3)利用中间函数
第三种种方法是对第一种方法的改进,避免子类的原型更改影响到父类的原型,利用了一个中间函数:
1 | const Temp = function(){}; |
(4)通过父类的原型对象进行遍历、拷贝,从而实现继承:
1 | for (const i in Parent.prototype) { |
同时继承实例属性和原型属性
(1)让子对象的原型成为父对象的实例:
1 | Child.prototype = new Father(); |
(2)通过ES6中的方法class
和extends
实现继承:
1 | class Father { |
要注意的是,class
只能在原型链上定义方法,所以也只能继承原型方法,而不能继承原型属性。
非构造函数的继承
让一个对象去继承一个不相关对象,由于这两个对象都是普通对象,不是构造函数,所以无法使用构造函数方法实现继承。
通过Object.create
实现
Object.create
方法是直接通过原型,而非模拟类,来实现普通对象(非构造函数)之间的继承
1 | const child = Object.create(father, [childDescriptors]) |
实际上实现的是child.__proto__ === father
通过Object.setPrototypeOf
实现
Object.setPrototypeOf
和Object.create
都是更改原型链(设置__proto__
属性)的手段,Object.getPrototypeOf(a)
是用来获取对象的__proto__
属性
1 | Object.setPrototypeOf(child, father) |
这个方法实现的也是child.__proto__ === father
通过object
函数
可以自己编写一个函数,实现上面Object.setPrototypeOf()
和Object.create()
的(部分)功能
1 | function object(parent) { |
实际上,可以将这个方法看做Object.create()
的简易的polyfill。
通过拷贝实现
可以父对象进行遍历、拷贝实现
浅拷贝:
1 | function extendCopy(p) { |
深拷贝:
1 | function deepCopy(p, c) { |
关于深拷贝、浅拷贝可以参考以前写过的笔记《JS05 JS中的拷贝》
ES6中的继承
ES6里面使用class
和extends
实现的继承,实际上是ES5中的继承方法的语法糖,它可以实现实例属性、原型方法、静态属性的继承
- 类的实例属性是通过
class
内部的constructor
里的super
实现的 - 类的原型方法是通过
Object.setPrototypeOf(Child.prototype, Father.prototype)
实现的( - 类的静态属性是通过
Object.setPrototypeOf(Child, Father)
实现的
使用ES6的继承方法,与ES5中的继承方法相比,不同点主要有以下下几点:
(1)实现的顺序
ES5的继承,实质是先创造子类的实例对象this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。
ES6的继承机制完全不同,子类一开始并没有自己的this
,而是需要先创造父类的实例对象this
(所以必须先调用super
方法),然后再用子类的构造函数修改this。
(2)静态方法和静态属性
ES6的继承方法可以继承父类的静态方法和静态属性,这一点是ES5的继承方法做不到的。
(3)无法继承原型属性,只能继承原型方法
由于在class
内部定义在constructor
之外的方法实际上都是原型方法,并不能定义原型属性,所以继承的自然也只能是原型方法,而无法继承原型属性
(4)原型方法的可枚举性
由于class
内部定义的方法都是不可枚举的,这一点与ES5的行为是不一致的
(5)变量提示
ES5的类是通过函数实现的,存在变量提升;而class
不存在变量提升
new
的过程
在通过构造函数实例化一个对象(new
)的过程中
1 | let p = new Person() |
发生了以下的过程:
1 | // 1 新建一个对象 |
new
运算的返回值默认返回this
,但显式的返回值时,如果返回值的是基本类型,则忽略返回值,仍然返回this
,如果返回值是引用类型,则直接返回该返回值作为对象的结果