JavaScript中的继承
JavaScript
中的继承
继承
JavaScript
的继承是基于原型链的。
通俗点讲就是每个对象都可以通过__proto__
指向另一个对象。
在寻找对象的属性的时候,会依次的遍历原型链上的对象。
找到就返回。
所有的对象的原型链最后都会指向Object.prototype
。
而Object.prototype
的原型对象为空,可以通过__proto__
看出来。
所以所有的对象都可以使用挂载在Object.prototype
的方法,前提是你不手贱把对象的原型置为null
。
原型只能是对象或者null
。
如果通过Object.setPrototypeOf
设置成基本变量报错。
如果通过__proto__
设置,不会报错,但是无效。
推荐使用之前说过的Reflect.setPrototypeOf
来设置对象的原型。
因为它的返回更加的符合程序的逻辑,当然该报错的地方一样会报错。
设置成功Reflect.setPrototypeOf
返回的是一个布尔值。
而Object.setPrototypeOf
返回原对象。
好像讲偏了…
JavaScript
中对于继承的多数方法,基本上都是和原型这一概念打交道。
在简单的继承体系中,即原型链,对于引用类型的值来说,他的子类共享同一份数据
导致了修改一个实例化对象的超类的数据,另一个实例化对象的对应数据都会发生变化。
1 | function Type() { |
上述代码对于t1
的修改影响到了t2
,显然这不是我们想看到的
借用构造函数
这种方法也叫做伪造对象或者经典继承
借用构造,借用借用,借别人的构造函数来使用,便是这个方法的原理
1 | function SuperType() { |
上面这么写其实有一点问题。
如果父类构造函数中存在name
字段的话,在子类中设置的就会被覆盖掉。
所以需要改变一下调用的顺序。
1 | function SubType(name) { |
如果单纯使用借用构造,那么函数就无法复用。
一般,如果想去定义一个类,都是在函数体内设置属性。
然后通过函数对象特有的prototype
来挂载方法。
这样实例化的对象的方法都是指向一个函数,从而实现了复用。
而直接通过this
挂载方法,则每次new
对象都会产生新的函数。
1 | function Type(name) { |
仅仅使用借用构造,就必须把所有的方法直接挂载在this
上。
因为借用构造无法获取父类原型上的方法和属性。
组合继承
组合继承也成为伪经典继承。
借鉴了借用构造和原型链各自的优势,组合起来。
借用构造的优势是解决了父类引用属性的共享问题。
原型链解决了函数的复用问题。
1 | function SuperType() { |
这时候整条链条是这样的
s1 --> SubType.prototype(SuperType实例化的对象) --> SuperType.prototype --> Object.prototype
这时候instanceof
和isPrototypeOf
都能够符合预期正常地工作
1 | console.log(s1 instanceof SubType); // --> true |
原型式继承
因为JavaScript
是一门很灵活的语言。
很多时候对象的结构可以很容易的改变。
1 | const o = {}; |
原型式继承不创建自定义的类型,而是通过原型链来生成匿名子类,从而生成子对象。
1 | function object(o) { |
函数内部定义临时函数F
。
改变F
的原型对象(prototype
)。
通过F
生成新的对象并返回。
在内置的函数中Object.create
和这个函数的行为一致。
区别是Object.create
第二个参数可以传入属性描述符对象。
1 | const o = Object.create( |
寄生式继承
寄生式继承可以说是在原型式继承的基础上而来的。
寄生式继承封装了由原型式继承创建而来的对象。
1 | function create(o) { |
很容易看出寄生式继承也无法复用函数,因为它是直接挂载在实例化对象上面的。
寄生组合式继承
寄生组合式继承对组合继承进行了优化。
在组合继承中,在子类的构造函数中调用了一次父类的构造函数SuperType.call
。
而在设置原型链中,又调用了一次父类的构造函数new SuperType
。
这样子类的原型上其实存在了和子类一样的属性。
1 | // ... |
对于设置子类的原型,没有必要去实例化一个父类的对象。
我们只需要一个指向父类原型对象的对象即可。
而这部分就通过原型式来实现。
1 | function extend(superType, subType) { |
这样子就只在子类的构造函数中调用了一次父类的构造函数。
而且也可以复用方法,属性也不会出现在原型链中了。
后记
JavaScript
中继承的最优解应该就是最后一种方式 - 寄生组合式继承。