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,如果返回值是引用类型,则直接返回该返回值作为对象的结果