说到继承,其它语言里可能有两种: 接口继承是继承方法签名,而实现继承则继承实际方法。ES函数没有签名,不能实现接口继承,只支持实现继承,而实现继承主要依靠原型链。(这两句话,说来轻松,理解来不易,且行且珍惜吧~)。
所以,理解原型链是掌握继承的必要条件。一个原型对象等与另一个类型的实例
function Parent(){ this.super = "parent"; this.friends = ["A", "B", "C"]; } Parent.prototype.getParentValue = function(){ return this.super; } function Child(){ this.sub = "Child"; } Child.prototype = new Parent(); Child.prototype.getChildValue = function(){ return this.sub; } var c1= new Child(); c1.getParentValue(); //"parent" c1.getChildValue(); //"Child" c1.constructor === Parent; //true c1.constructor === Child; //false
var c2= new Child(); c2.friends.push("D"); c2.friends //["A", "B", "C", "D"] c1.friends //["A", "B", "C", "D"]
为什么demo.constructor ===Parent;呢? 因为demo.prototype指向Parent实例,而Parent.prototype.constructor指向Parent,因此demo.constructor继承自 Parent.prototype,所以指向Parent;
使用原型练实现继承:说明:不能用对象字面量创建原型方法,因为这样会重写原型链。
缺点:1.引用类型值的原型属性会被所有实例共享,因此在构造函数中定义属性,但通过原型继承时,一个类型的实例会变成另一个对象的原型。因此实例中的属性就变成了现在的原型的属性了。2.没有办法在不影响所有对象的情况下,向超类型传参。
为了解决引用类型带来的问题——>借用构造函数(伪造对象、经典继承 ):在子类型构造函数的内部调用超类型构造函数
function Parent(){ this.friends = ["A", "B", "C"]; } function Child(){ Parent.call(this); this.age = 23; } var c1 = new Child(); var c2 = new Child(); c1.friends.push("D"); c1.friends //["A", "B", "C", "D"] c2.friends //["A", "B", "C"] c2.age //23
借用构造函数的缺点:无法避免构造函数模式的缺点,方法不能复用,而且超类原型中的方法,对于子类型是不可见的,所以只能统一使用构造函数模式。
为了避免这些缺点——>组合继承(伪经典继承):使用原型链实现对原型属性和方法的继承,而通过构造函数实现对实例属性的继承
function Parent(name){ this.name = name || "parent"; this.friends = ["A", "B", "C"]; } Parent.prototype.sayName = function (){ return this.name; } function Child(name, age){ Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); var c1 = new Child("Tom", 34); var c2 = new Child("Joe", 22); c1.friends.push("D"); c2.friends //["A", "B", "C"] c1.sayName(); //"Tom"
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点。成为js中最常用的继承模式。
此外还有几种继承模式——>原型式继承:借用原型基于已有的对象创建新的对象,同时还不必因此创建自定义类型。
function object(o){ function F(){} F.prototype = o; return new F(); } var person = { name: "Tom", friends: ["A", "B", "C"] }; var p1= object(person); p1.name = "Marry"; p1.friends.push("D"); var p2 = object(person); p2.name = "Joe"; p2.friends.push("E"); person.friends //["A", "B", "C", "D", "E"]
object()函数中创建了一个临时构造函数,并将传入的对象作为该构造函数的原型。相当于进行一次浅复制。和原型模式一样,引用类型始终会被共享。其中ES5定义了Object.create()方法规范了原型继承。
因此,在没必要兴师动众的创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式模式是个不错的选择。
——>寄生式继承:与原型式继承紧密相关的一种思路,与寄生构造函数和工厂模式类似,即创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象。
function createAnother(o){ var clone = object(o); clone.sayHi = function(){ return "HI"; }; return clone; } var person = { name: "Tom", friends: ["A", "B", "C"] }; var p = createAnother(person); p.sayHi() //"HI"
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式也是一种有用的模式,object()非必须,任何能返回新对象的函数都适用于此模式
组合模式是最常用的继承模式,但是组合模式两次调用超类型构造函数,
为了解决这个问题——>寄生组合模式:使用构造函数继承属性,通过原型链的混成形式继承方法;不必为了指定子类型的原型而调用超类型的构造函数,我们可以使用寄生式继承继承超类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype(C, P){ var prototype = object(P.prototype); prototype.constructor = C; C.prototype = prototype; } function Parent(name){ this.name = name || "parent"; this.friends = ["A", "B", "C"]; } Parent.prototype.sayName = function (){ return this.name; } function Child(name, age){ Parent.call(this, name); this.age = age; } inheritPrototype(Child, Parent); Child.prototype.sayAge = function(){ return this.age; };
组合继承模式:集寄生模式和组合模式优点于一身,是实现基于类型继承最有效最理想的方式。