一,基础知识
回顾 构造函数、原型对象、实例对象三者之间的关系:
构造函数的 prototype 属性 值为 原型对象。
原型对象的 constructor 属性 值为 构造函数。
实例对象的 [[prototype]] 内部属性(__proto__ 属性) 值为 其构造函数的 prototype 属性值。
获取对象(包括了函数和数组)的 [[prototype]] 内部属性值有两种方式:
1,Object.getPrototypeOf() 方法
2,__proto__ 属性
构造函数的 prototype 属性值有两种情况:
1,原型对象的 constructor 属性,值为 构造函数。
2,实例对象的 [[prototype]] 内部属性,值为 该实例对象的构造函数的 prototype 属性值。
我理解的继承:
把一个实例对象 赋值给 一个函数的原型,这个函数就继承了这个实例对象的构造函数。
如何去判断继承:
1,子类 vs 父类
如果 SubA 继承自某构造函数,那么 SubA 的 prototype 属性指向这个构造函数的实例对象。
方法一:
通过这个实例的 constructor 属性找到这个构造函数,就能确定其父类。
方法二:
在 constructor 丢失的情况下,
通过这个实例的 [[prototype]] 内部属性或者 __proto__ 属性,就能找到其父类的原型对象。
1 | function A () {} |
2,子类实例 vs 父类
子类实例的 [[prototype]] 内部属性指向父类的实例。
1 | function A () {} |
二,ES5中的继承模式
1、原型链继承
把 父类的实例对象 作为 子类的 prototype 属性的值。
1 | function SuperType () { |
原型链的问题:
在通过原型来实现继承时,原型实际上会变成另一个类型的实例,
于是,另一个类型上原先的实例属性也就变成了现在的原型属性。
1 | function SuperType() { |
2、借用构造函数继承
在子类构造函数的内部调用父类构造函数。
1 | function SuperType (name) { |
优点:
相对于原型链而言,这种方式可以在子类构造函数中向父类构造函数传递参数。
缺点:
方法都在构造函数中定义,无法复用,父类定义在原型中的方法,对于子类而言不可见。
3、组合继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
1 | function SuperType (name) { |
缺点:
1,在创建子类原型时,调用了父类的构造函数,写入所有父类的实例属性。
2,在调用子类构造函数时,重写这些属性。
4、原型式继承 和 Object.create()
原型式继承没有使用严格意义上的构造函数,借助原型可以基于已有对象创建新对象。
ECMAScript 5 通过新增 Object.create() 方法规范了原型式继承。
1 | var person = { |
对象关联:
要创建一个合适的关联对象,建议使用 Object.create() 而不是使用具有副作用的 new SuperType()。
比较一下两种关联对象的方法:
1 | // ES6 之前需要抛弃默认的 SubType.prototype |
ES5 之前 Object.create() 的 polyfill
1 | if (typeof Object.create != 'function') { |
5、寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回该对象。
1 | function createAnother (original) { |
1 | // 传统的 JavaScript 类 Vehicle |
6、寄生组合式继承
组合式继承中两次调用父类构造函数的不足:
1,父类中的所有属性都会成为原型属性继承给子类。
2,在子类不得不调用父类构造函数,利用屏蔽属性的原理来重写非原型属性。
寄生组合式继承解决了上述问题。
1 | function inheritsPrototype (subType, superType) { |
JavaScript 中的差异化继承
差异化继承是基于原型编程的一个常见模型,它讲的是大部分的对象是从其他更一般的对象中派生而来的的,只是在一些很小的地方进行了修改。每个对象维护一个指向它们的 prototype 的引用和一个差异化属性列表。
1 | Object.prototype.clone = function(){ |
三、多态
相对多态与显式多态
相对多态是不指定想要访问的绝对继承层次,而是使用相对的“查找上一层”,参考 ES6 中的 super。
在 ES6 之前,并没有相对多态的机制,需要通过名称显式指定父类并调用它的方法,称之为显式多态。
关联性
多态并不表示子类和父类有关联,子类得到的只是父类的一个实例,子类对继承到的一个方法进行重写,并不会影响到父类中的方法。
四、混入(多重继承)
混入是 JavaScript 中实现多重继承的一种方式。
显式混入
1 | function mixin (sourceObj, targetObj) { |
实际上,在显式混入完成之后,两者之间仍然会有一些方法能影响到对方,例如引用同一个数组。
JavaScript 中只能复制对共享对象(包括了函数,数组)的引用,修改了父类的共享对象,会影响子类。
隐式混入
1 | var Something = { |
隐式混入是在子类的构造函数或者方法中调用了父类的方法,实现了把父类中的行为混入到子类中。
最后
文中提到的类,只是便于理解和区别与其他函数。
它不同于面向对象中的类,它在实例化和继承中不能被完全复制。
多态看起来似乎是从子类引用父类,但本质上引用的其实是复制的结果。
参考
An easy way to understand JavaScript’s prototypal inheritance
Prototypes as classes – an introduction to JavaScript inheritance