JavaScript———继承
JavaScript中继承的方式
原型链继承
// 父类
function SuperType (colors = ['red', 'blue', 'green']) {
this.colors = colors;
}
// 子类
function SubType () {}
// 继承父类
SubType.prototype = new SuperType();
// 以这种方式将 constructor 属性指回 SubType 会改变 constructor 为可遍历属性
SubType.prototype.constructor = SubType;
let superInstance1 = new SuperType(['yellow', 'pink']);
let subInstance1 = new SubType();
let subInstance2 = new SubType();
superInstance1.colors; // => ['yellow', 'pink']
subInstance1.colors; // => ['red', 'blue', 'green']
subInstance2.colors; // => ['red', 'blue', 'green']
subInstance1.colors.push('black');
subInstance1.colors; // => ['red', 'blue', 'green', 'black']
subInstance2.colors; // => ['red', 'blue', 'green', 'black']
上述代码使用了最基本的原型链继承使得子类能够继承父类的属性,**原型继承的关键步骤就在于:将子类原型和父类原型关联起来,使原型链能够衔接上,**这边是直接将子类原型指向了父类实例来完成关联。
上述是原型继承的一种最初始的状态,我们分析上面代码,会发现还是会有问题:
- 在创建子类实例的时候,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的
构造函数传递参数。 - 这样创建的子类原型会包含父类的实例属性,造成引用类型属性同步修改的问题。
借用构造函数
// 借用构造函数
function SuperType(name) {
this.name = name;
}
function SubType() {
//继承了 SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。
而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
组合继承
组合继承通过组合借用构造函数和原型链继承来实现继承;
其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
// 组合继承实现
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = new Parent();
const child = new Child(1)
child.getValue();
child instanceof Parent;
然而它还是存在问题:父类的构造函数被调用了两次(创建子类原型时调用了一次,创建子类实例时又调用了一次),导致子类原型上会存在父类实例属性,浪费内存。
原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
// 原型式继承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
从本质上讲, object()对传入其中的对象执行了一次浅复制
**ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。**这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
在传入一个参数的情况下,Object.create()与 object()方法的行为相同。
Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型链继承一样。
寄生式继承
寄生式( parasitic)继承是与原型式继承紧密相关的一种思路;创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
// 寄生式继承
function createAnother(original) {
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function() { //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
object()函数不是必需的;任何能够返回新对象的函数都适用于此模式。
问题:
使用寄生继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。
寄生组合式继承
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
// 寄生组合继承实现
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //复制原型对象
prototype.constructor = subType; //修改复制的原型对象的constructor属性,指向子类
subType.prototype = prototype; // 将原型对象复制给子类的原型
}
function Parent(value) {
this.value = value;
}
Parent.prototype.getValue = function() {
console.log(this.value);
}
function Child(value) {
Parent.call(this, value)
}
inheritPrototype(Child,Parent)
// 或者通过 Object.create()
// Child.prototype = Object.create(Parent.prototype, {
// constructor: {
// value: Child,
// enumerable: false, // 不可枚举该属性
// writable: true, // 可改写该属性
// configurable: true // 可用 delete 删除该属性
// }
// })
const child = new Child(1)
child.getValue();
child instanceof Parent;
寄生组合继承的模式是现在业内公认的比较可靠的 JS 继承模式,ES6 的 class 继承在 babel 转义后,底层也是使用的寄生组合继承的方式实现的。
继承关系判断
instanceof
我们可以使用 instanceof 来判断二者间是否有继承关系,instanceof 的字面意思就是:xx 是否为 xxx 的实例。如果是则返回 true 否则返回 false:
function Parent () {}
function Child () {}
Child.prototype = new Parent();
let parent = new Parent();
let child = new Child();
parent instanceof Parent; // => true
child instanceof Child; // => true
child instanceof Parent; // => true
child instanceof Object; // => true
instanceof 本质上是通过原型链查找来判断继承关系的,因此只能用来判断引用类型,对基本类型无效
Object.prototype.isPrototypeOf(obj)
还可以利用 Object.prototype.isPrototypeOf 来间接判断继承关系,该方法用于判断一个对象是否存在于另一个对象的原型链上:
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true