javascript 中对于继承的描述:javascript
许多面向对象语言都支持两种继承的方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。在 javascript 中因为函数没有签名也就没法实现接口继承,而只支持实现继承,并且实现继承主要经过原型链来实现的。java
先引述下官方文档对于原型链的描述:其基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法。要理解这个概念要先弄清楚构造函数,原型,和实例的关系:每一个构造函数(只要是函数)都有一个 prototype 属性该属性指向一个对象(这个对象就是构造函数的原型对象);原型对象(只要是对象)中都有一个 constructor 属性该属性指向一个构造函数;而实例中都包含一个指向原型对象的内部指针 `Prototype`。说白了就是原型链的构建是经过将一个类型的实例赋值给另外一个构造函数的原型实现的。这样子类型就能够访问定义在超类型上的全部属性和方法了。每一个对象都有本身的原型对象,以原型对象为模板从原型对象中继承属性和方法,原型对象也能够有本身的原型并从中继承属性和方法,一层一层,以此类推,这种关系被称为原型链它解释了为什么一个对象会拥有定义在其余对象上的属性和方法。
app
javascript 中实现继承的六种方式:ide
一、原型链函数
二、借用构造函数
this
三、组合继承(组合使用原型链和借用构造函数)
spa
四、原型式继承
prototype
五、寄生式继承
指针
六、寄生组合式继承(组合使用组合继承和寄生式继承)orm
一、原型链
具体示例:
// 实现原型链的一种基本模式
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
// 继承,用 SuperType 类型的一个实例来重写 SubType 类型的原型对象
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); // true
PS:SubType 继承了 SuperType,而继承是经过建立 SuperType 的实例,并将该实例赋值给 SubType 的原型实现的。实现的本质是重写子类型的原型对象,代之以一个新类型的实例。子类型的新原型对象中有一个内部属性 `Prototype` 指向了 SuperType 的原型,还有一个从 SuperType 原型中继承过来的属性 constructor 指向了 SuperType 构造函数。最终的原型链是这样的:instance 指向 SubType 的原型,SubType 的原型又指向 SuperType 的原型,SuperType 的原型又指向 Object 的原型(全部函数的默认原型都是 Object 的实例,所以默认原型都会包含一个内部指针,指向 Object.prototype)。
原型链的缺点:
一、在经过原型来实现继承时,原型实际上会变成另外一个类型的实例。因而,原先的实例属性也就瓜熟蒂落地变成了如今的原型属性,而且会被全部的实例共享。这样理解:在超类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被全部子类型实例所共享。
二、在建立子类型的实例时,不能向超类型的构造函数中传递参数。
二、借用构造函数(也称伪造对象或经典继承)
具体示例:
// 在子类型构造函数的内部调用超类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上
function SuperType(){
// 定义引用类型值属性
this.colors = ["red","green","blue"];
}
function SubType(){
// 继承 SuperType,在这里还能够给超类型构造函数传参
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("purple");
alert(instance1.colors); // "red,green,blue,purple"
var instance2 = new SubType();
alert(instance2.colors); // "red,green,blue"
PS:经过使用 apply() 或 call() 方法,咱们其实是在将要建立的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType() 函数中定义的全部对象初始化代码。结果 SubType 的每一个实例就都会具备本身的 colors 属性的副本了。
借用构造函数的优势是解决了原型链实现继承存在的两个问题;借用构造函数的缺点是方法都在构造函数中定义,所以函数复用就没法实现了。并且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果全部类型都只能使用构造函数模式。
三、组合继承(也称伪经典继承)
具体示例:
// 将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承。这样,既经过在原型上定义方法实现了函数复用,又可以保证每一个实例都有本身的属性。
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(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(){
alert(this.age);
};
var instance1 = new SubType("luochen",22);
instance1.colors.push("purple");
alert(instance1.colors); // "red,green,blue,purple"
instance1.sayName();
instance1.sayAge();
var instance2 = new SubType("tom",34);
alert(instance2.colors); // "red,green,blue"
instance2.sayName();
instance2.sayAge();
PS:组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势,成为 javascript 中最经常使用的继承模式。并且,使用 instanceof 操做符和 isPrototype() 方法也可以用于识别基于组合继承建立的对象。可是,它也有本身的不足。最大的问题是不管在什么状况下,都会调用两次超类型构造函数:一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部。
四、原型式继承
具体示例:
// 借助原型能够基于已有的对象建立新对象,同时还没必要所以建立自定义类型。
一、自定义一个函数来实现原型式继承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
PS:在 object() 函数内部,先建立一个临时性的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时类型的一个新实例。实质上,object() 对传入其中的对象执行了一次浅复制。
二、使用 Object.create() 方法实现原型式继承。这个方法接收两个参数:一是用做新对象原型的对象和一个为新对象定义额外属性的对象。在传入一个参数的状况下,此方法与 object() 方法做用一致。
在传入第二个参数的状况下,指定的任何属性都会覆盖原型对象上的同名属性。
var person = {
name: "luochen",
colors: ["red","green","blue"]
};
var anotherPerson1 = Object.create(person,{
name: {
value: "tom"
}
});
var anotherPerson2 = Object.create(person,{
name: {
value: "jerry"
}
});
anotherPerson1.colors.push("purple");
alert(anotherPerson1.name); // "tom"
alert(anotherPerson2.name); // "jerry"
alert(anotherPerson1.colors); // "red,green,blue,purple"
alert(anotherPerson2.colors); // "red,green,bule,purple";
PS:只是想让一个对象与另外一个对象相似的状况下,原型式继承是彻底能够胜任的。可是缺点是:包含引用类型值的属性始终都会共享相应的值。
五、寄生式继承
具体示例:
// 建立一个仅用于封装继承过程的函数,该函数在内部以某种方式来加强对象,最后返回这个对象
function createPerson(original){
var clone = Object.create(original); // 经过 Object.create() 函数建立一个新对象
clone.sayGood = function(){ // 加强这个对象
alert("hello world!!!");
};
return clone; // 返回这个对象
}
PS:在主要考虑对象而不是自定义类型和构造函数的状况下,寄生式继承也是一种有用的模式。此模式的缺点是作不到函数复用。
六、寄生组合式继承
具体示例:// 经过借用构造函数来继承属性,经过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,而后再将结果指定给子类型的原型function SuperType(name){ this.name = name; this.colors = ["red","green","blue"];}SuperType.prototype.sayName = function(){ alert(this.name);};function SubType(name,age){ SuperType.call(this,name); this.age = age;}// 建立超类型原型的一个副本var anotherPrototype = Object.create(SuperType.prototype);// 重设因重写原型而失去的默认的 constructor 属性anotherPrototype.constructor = SubType;// 将新建立的对象赋值给子类型的原型SubType.prototype = anotherPrototype;SubType.prototype.sayAge = function(){ alert(this.age);};var instance1 = new SubType("luochen",22);instance1.colors.push("purple");alert(instance1.colors); // "red,green,blue,purple"instance1.sayName();instance1.sayAge();var instance2 = new SubType("tom",34);alert(instance2.colors); // "red,green,blue"instance2.sayName();instance2.sayAge();PS:这个例子的高效率体如今它只调用一次 SuperType 构造函数,而且所以避免了在 SubType.prototype 上面建立没必要要,多余的属性。与此同时,原型链还能保持不变;所以还可以正常使用 instance 操做符和 isPrototype() 方法。