继承
每个构造函数(constructor)都有一个原型对象(prototype),每个实例对像包含一个指向原型对象的指针(__proto__
)。
原型链继承
每个构造函数有一个原型对象,原型对象包含一个指向构造函数的指针,而实例包含一个指向原型对象的内部指针。如果我们让原型对象等于另一个类型的实例,那么此时的原型对象就包含了一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。如果我们再更新原型对象,那么就会形成一个原型链。
优点:原型链继承简单易理解,实现起来也相对容易。
缺点:原型链上的所有属性和方法都是共享的,任何一个对象对属性的修改都会反映在其他所有实例上,这种特性在某些情况下可能会造成问题。另外,不能在创建对象时向构造函数传递参数。
js
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true
构造函数继承
在子类型构造函数的内部调用超类型构造函数。通过使用 .call()
或 .apply()
方法,可以在新创建的对象(存在于子类型构造函数的环境中)上执行超类型构造函数。
优点:可以在子类型构造函数中向超类型构造函数传递参数。
缺点:方法都在构造函数中定义,因此函数复用就无从谈起。而且超类型原型中定义的方法,对子类型而言也是不可见的。
js
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
// 继承了 SuperType
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"
组合继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
优点:既可以实现函数复用,又可以保持每个实例拥有自己的属性。
缺点:调用了两次超类构造函数,生成了两份实例。
js
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name); // 继承属性
this.age = age;
}
SubType.prototype = new SuperType(); // 继承方法
SubType.prototype.constructor = SubType; // 修复构造函数引用
SubType.prototype.sayAge = function () {
console.log(this.age);
};
let instance1 = new SubType("Bob", 23);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Bob"
instance1.sayAge(); // 23
寄生组合继承
最优,类似 ES6 extends
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
优点:避免了在原型链和构造函数上面的重复,是 JavaScript 中最理想的继承范式。
js
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name); // 第二次调用 SuperType()
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age);
};
let instance1 = new SubType("Bob", 23);
instance1.sayName(); // "Bob"
instance1.sayAge(); // 23