六大继承方案总结(继承系列-3)
目录:
原型链继承
问题:
借用构造函数(不算标准的继承模式)
组合继承(伪经典继承)
核心思想:
优点
问题:(寄生组合式继承就是为了解决这一问题)
原型式继承
实现原理:
Object.create()
优点
缺点
寄生式继承
原理
缺点
寄生组合式继承
实现思路
核心逻辑
优点
圣杯模式
原型链继承
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;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
问题:
最主要的问题来自包含引用类型值的原型。我们前面介绍过包含引用类型值的原型属性会被所有实例共享;而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。
借用构造函数(不算标准的继承模式)
又叫“对象伪装”或“经典继承”
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
function SubType() {
// 继承了 SuperType
SuperType.call(this,'同时传递参数');
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"
console.log(instance2.name); // '同时传递参数'
解决了“引用类型值的原型属性会被所有实例共享”的问题。
组合继承(伪经典继承)
综合了原型链和盗用构造函数,将两者的优点集中了起来。
核心思想:
使用借用构造函数的技术实现“实例属性”和方法的继承,使用原型链实现“原型属性”和方法的继承。
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实例的name,colors属性
// 自定义自己的实例属性
this.age = age;
}
SubType.prototype = new SuperType(); // (第二次调用构造函数)利用“构造原型链”的思想,继承原型属性和方法
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
优点
❝组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继 承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。
❞
问题:(寄生组合式继承就是为了解决这一问题)
但依旧拥有原型继承和借用构造函数的缺点:
组合继承最大的问题就是效率问题,因为无论在什么情况下,都会调用两次构造函数: 一次是在创建子类型原型时, 另一次是在子类型构造函数内部。 第二个问题是会继承多余的属性。
上例中,使用SubType.prototype = new SuperType();
,SubType将会继承SuperType实例对象的name、colors属性,虽然name没有被创建。因此造成了一个问题,SubType.prototype上会存在不必要的,多余的属性。如name,colors属性同时存在于SubType.prototype和SubType中。
原型式继承
实现原理:
创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
使用
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
Object.create()
规范化了原型式继承的概念,用于实现原型式继承
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
优点
不需要单独创建构造函数
缺点
属性中包含的引用值始终会在相关对象间共享
寄生式继承
与原型式继承比较接近,相当于把原型式继承又包装了一遍。
原理
思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
基本的寄生继承模式如下:
function createAnother(original) {
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function () { // 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象
}
适合主要关注对象,而不在乎类型和构造函数的场景
缺点
通过寄生式继承给对象添加函数会导致函数难以重用(比如上述代码的sayHi)
寄生组合式继承
实现思路
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。
基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型
概括:
通过借用构造函数来继承属性; 通过原型链来继承方法(圣杯式继承)。
核心逻辑
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 赋值对象
}
这个函数接收两个参数: 子类构造函数和父类构造函数。
函数内部,第一步是创建父类原型的一个副本。
给返回的 prototype 对象设置 constructor 属性,为了解决由于重写原型导致默认 constructor 丢失的问题。
将新创建的对象赋值给子类型的原型。
优点
只调用了一次构造函数,避免了子类原型上不必要也用不到的属性,因此可以说这个例子的效率更高。
而且,原型链仍然保持不变,因此 instanceof 操作符和 isPrototypeOf()方法正常有效。
寄生式组合继承可以算是引用类型继承的最佳模式。
圣杯模式
最后发现,这不就是圣杯模式的写法么!
function inherit(Target,Origin){
function F(){};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constuctor = Target;
target.prototype.uber = Origin.prototype;
}
用圣杯式原型链继承方法替代原来的new 构造原型链的方法,这样儿子身上就没有父亲身上的实例化属性了!good!
让我们一起携手同走前端路!
关注公众号回复【加群】即可