继承
许多 OO 语言支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在 ECMAScript 中无法实现接口继承。 ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现。
原型链
基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
js
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType() { this.subproperty = false; } //继承了 SuperType SubType.prototype = new SuperType(); SuperType.prototype.getSubValue = function() { return this.subproperty; } var instance = new SubType(); alert(instance.getSuperValue());//true
-
别忘记默认的原型
-
确定原型和实例的关系
js
alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true
js
alert(Object.prototype.isPrototypeOf(instance));//ture alert(SuperType.prototype.isPrototypeOf(instance));//ture alert(SubType.prototype.isPrototypeOf(instance));//ture
-
谨慎地定义方法
给原型添加方法的代码一定要放在替换原型的语句之后。
js
function SuperType() { this.property = true; } SuperType.protype.getSuperValue = function() { return this.property; } function SubType() { this.subproperty = false; } //继承了 SuperType SubType.prototype = new SuperType(); //添加新方法 SubType.prototype.getSubValue = function() { return this.subproperty; }; //重写超类型中的方法 SubType.prototype.getSuperValue = function() { return false; }; var instance = new SubType(); alert(instance.getSuperValue());//false
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; }, someOtherMethod = function() { return false; } }; var instance = new SubType(); alert(instance.getSuperValue()); //error 原型链被切断(现在的原型包含的是 Object 的实例)。
4.原型链的问题
最主要的问题来自包含引用类型值的原型。前面已经介绍过包含引用类型值的原型属性会被所有实例共享,这也是为什么要在构造函数中而不在原型对象中定义属性的原因。
第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给炒类型的构造函数传递参数。
借用构造函数(伪造对象或经典继承)
即在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,所以可通过 apply()
或 call()
方法也可以在(将来)新创建的对象上执行构造函数。
js
function SuperType() { this.colors = ["red","blue","green"]; } function SubType() { //继承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
传递参数
js
function SuperType(name) { this.name = name; } function SubType() { //继承了 SuperType,同时还传递了参数 SuperType.call(this,"PaddingMe"); //实例属性 this.age = 25; } var instance = new SubType(); alert(instance.name); //"PaddingMe" alert(instance.age); //25
组合继承(伪经典继承)
即将原型链和借用构造函数的方法组合在一起,思路为使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
js
function SuperType(name) { this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function() { alert(this.name); } function SubType() { //继承属性 SuperType.call(this,name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.constuctor = SubType(); SubType.prototype.sayAge = function() { alert(this.age); } var instance1 = new("PaddingMe",25); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName; //"PaddingMe" instance1.sayAge; //25 var instance2 = new("hw",26); alert(instance2.colors); //"red,blue,green" instance2.sayName; //"hw" instance2.sayAge; //26
原型式继承
js
function object(o) { function F(){} F.prototype = o; return new F(); } var person = { name : "PaddingMe"; friends :["hw","wjj","hz"]; } var antherPerson = object(person); antherPerson.name = "Hhb"; antherPerson.friends.push("zxp"); var yetAntherPerson = object(person); yetAntherPerson.name = "Linda"; yetAntherPerson.friends.push("him"); alert(person.friends)//"hw,wjj,hz,zxp,him"
ECMAScirpt 5 中新增 Object.create() 方法规范化了原型式继承。有两个参数,一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
在传入一个参数情况下,Objetc.create() 和 object() 方法的行为相同。
js
var person = { name : "PaddingMe"; friends :["hw","wjj","hz"]; } var antherPerson = Object.create(person); antherPerson.name = "Hhb"; antherPerson.friends.push("zxp"); var yetAntherPerson = Object.create(person); yetAntherPerson.name = "Linda"; yetAntherPerson.friends.push("him"); alert(person.friends)//"hw,wjj,hz,zxp,him"
第二个参数与 `Object.defineProperties() 方法的第二个参数格式相同: 每个属性都是通过自己的描述符定义的。
js
var person = { name : "PaddingMe"; friends :["hw","wjj","hz"]; } var anthorPerson = Object.create(person, { name: { value:"hehe"; } }) alert(anthorPerson.name);//"hehe"
寄生式继承
即创建一个仅用于封装继承构成的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
js
function createAnother(original) { var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function() { //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 } var person = { name : "PaddingMe"; friends :["hw","wjj","hz"]; } var anthorPerson = createAnother(person); anthorPerson.sayHi();//"hi"
寄生组合式继承
js
function SuperType(name) { this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function() { alert(this.name); } function SubType(name,age) { SuperType.call(this,name); //第二次调用SuperType(); this.age = age; } SubType.prototype = new SuperType(); // 第一次调用SuperType(); SubType.prototype.constuctor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路为:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结构指定给子类型的原型。寄生组合式继承的基本模式:
js
function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; // 指定对象 }
js
function SuperType(name) { this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function() { alert(this.name); } function SubType(name,age) { SuperType.call(this,name); //第二次调用SuperType(); this.age = age; } inheritPrototype(SubType,SuperType); SubType.prototype.sayAge = function(){ alert(this.age); }