在JS继承(一)中总结了继承的原型链继承、盗用构造函数继承以及组合继承三种继承方式,当然js中的继承方式不仅仅这三种方式,所以这次再总结一下其它继承模式;
原型式继承
原型式继承使用情况:你有一个对象,想在它的基础上再创建一个新对象。
通过Object.create()实现,这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象。
let person = {
name: "Jackson",
friends: ["Jimi","Van","Jhone"]
};
let anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");
console.log(person.friends) // "Jimi","Van","Jhone","Rob"
let yetAnotherPerson = Object.create(person);
yetAnotherPerson .friends.push("James");
console.log(person.friends) // "Jimi","Van","Jhone","Rob","James"
Object.create()的第二个参数与Object.defineProperties()的第二个参数一样:每个新增的双属性都通过各自的描述符来描述。以这种方式添加的属性会遮蔽原型对象上的同名属性
let person = {
name: "Jackson",
friends: ["Jimi","Van","Jhone"]
};
let anotherPerson = Object.create(person,{
name: {
value: "Greg"
}
});
console.log(anotherPerson.name) // "Greg"
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。
寄生式继承
寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function object(o){
function F(){}
F.prototypr = o;
return new F();
}
function createAnother(original){
let clone= object(original);
clone,sayHi = function(){
console.log("hi");
};
return clone;
}
在这段代码中,createAnother()函数接收一个参数,就是新对象的基准对象。这个对象original会被传给object()函数,然后将返回的新对象赋值给clone。接着给clone对象添加一个新方法sayHi()。最后返回这个对象。可以像下面这样使用createAnother()函数:
let person = {
name: "Jackson",
friends: ["Jimi","Van","Jhone"]
};
let anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "hi"
这个例子基于person 对象返回一个新对象。新返回的anotherPerson对象具有person的所有属性和方法,还有一个新方法加sayHi()。
寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。
注意: 通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。
寄生式组合继承
之前说到的组合继承其实也存在效率问题,最主要的效率问题就是父类构造函数始终会被调用两次:一次是在创建子类原型时调用,另一次是在子类构造函数中调用。本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了,我们再来回顾一下组合继承:
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()z
this.age = age;
}
//继承方法
SubType.prototype = new SuperType(); // 第一次调用SuperType()
SubType.prototype.sayAge = function(){
console.log(this.age);
}
在上面的代码执行后,SubType.prototype上会有两个属性:name和colors。它们都是SuperType的实例属性,但现在成为了SubType的原型属性。在调用SubType构造函数时,也会调用SuperType构造函数,这一次会在新对象上创建实例属性name和colors。这两个实例属性会遮蔽原型上同名的属性。
这样的话就会有两组name和colors属性:一组在实例上;另一组在SubType的原型上。这就是调用两次SuperType构造函数的结果。
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类型构造函数给子类型原型赋值,而是取得父类型原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的对象赋值给子类原型。寄生式组合继承的基本模式如下:
function inheritPrototype(subType,superType){
let prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
这个inheritPrototype()函数实现了寄生式组合继承的核心逻辑。这个函数接收两个参数:子类构造函数和父类构造函数。在这个函数内部,第一步是创建父类原型的一个副本。然后,给返回的prototype对象设置constructor属性,解决由于重写默认导致默认constructor丢失的问题,最后将新创建的对象赋值给子类型的原型。如下例所示,调用inheritPrototype()就可以实现前面例子中的子类型原型赋值:
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()z
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
}
这里只调用了一次SuperType构造函数,避免了SubType.prototype上不必要也用不到的属性,因此可以说这个例子效率更高。寄生式组合继承可以算是引用类型继承的最佳模式。
至于ES6中新增的类(class)就不在这里说了。