每日三问之javascript面向对象之继承

原型链

构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
实现原型链有一种基本模式,其代码大致如下。
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

所有引用类型默认都继承了 Object,而这个继承也是通过原型链实现的

展示了该例子中完整的原型链。
一句话, SubType 继承了 SuperType ,而 SuperType 继承了 Object 。当调用 instance.toString() 时,实际上调用的是保存在 Object.prototype 中的那个方法。

确定原型和实例的关系 

第一种方式是使用 instanceof 操作符
alert(instance instanceof Object); //true 
alert(instance instanceof SuperType); //true 
alert(instance instanceof SubType); //true
第二种方式是使用 isPrototypeOf() 方法
alert(Object.prototype.isPrototypeOf(instance)); //true 
alert(SuperType.prototype.isPrototypeOf(instance)); //true 
alert(SubType.prototype.isPrototypeOf(instance)); //true
谨慎地定义方法
子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法
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; 
}; 
//重写超类型中的方法
SubType.prototype.getSuperValue = function (){ 
 return false; 
}; 
var instance = new SubType(); 
alert(instance.getSuperValue()); //false
重写这个方法将会屏蔽原来的那个方法.
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。
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!
原型链的问题
最主要的问题来自包含引用类型值的原型。引用类型值的原型属性会被所有实例共享,在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。
原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。
function SuperType(){ 
 this.colors = ["red", "blue", "green"];
} 
function SubType(){ 
} 
//继承了 SuperType 
SubType.prototype = new SuperType(); 
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,black"

借用构造函数 

函数只不过是在特定环境中执行代码的对象,因此通过使用 apply() call() 方法也可以在(将来)新创建的对象上执行构造函数。
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"
传递参数
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函 数传递参数。
function SuperType(name){ 
 this.name = name; 
} 
function SubType(){ 
 //继承了 SuperType,同时还传递了参数
 SuperType.call(this, "Nicholas"); 
 
 //实例属性
 this.age = 29; 
} 
var instance = new SubType(); 
alert(instance.name); //"Nicholas"; 
alert(instance.age); //29

借用构造函数的问题

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定
义,因此函数复用就无从谈起了。

组合继承

组合继承( combination inheritance ),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
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); 
 
 this.age = age; 
} 
//继承方法
SubType.prototype = new SuperType(); 
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){ 
 alert(this.age); 
}; 
var instance1 = new SubType("Nicholas", 29); 
instance1.colors.push("black"); 
alert(instance1.colors); //"red,blue,green,black" 
instance1.sayName(); //"Nicholas"; 
instance1.sayAge(); //29 
var instance2 = new SubType("Greg", 27); 
alert(instance2.colors); //"red,blue,green" 
instance2.sayName(); //"Greg"; 
instance2.sayAge(); //27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceof isPrototypeOf() 也能够用于识别基于组合继承创建的对象。

原型式继承 

ECMAScript 5 通过新增 Object.create() 方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
var person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
var anotherPerson = Object.create(person); 
anotherPerson.name = "Greg"; 
anotherPerson.friends.push("Rob"); 
 
var yetAnotherPerson = Object.create(person); 
yetAnotherPerson.name = "Linda"; 
yetAnotherPerson.friends.push("Barbie"); 
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。
var person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
};
var anotherPerson = Object.create(person, { 
 name: { 
 value: "Greg" 
 } 
}); 
 
alert(anotherPerson.name); //"Greg"
不过别忘了,原型式继承 包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){ 
 var clone = object(original); //通过调用函数创建一个新对象
 clone.sayHi = function(){ //以某种方式来增强这个对象
 alert("hi"); 
 }; 
 return clone; //返回这个对象
}

// 应用
var person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
var anotherPerson = createAnother(person); 
anotherPerson.sayHi(); //"hi"
注意:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。

寄生组合式继承

function inheritPrototype(subType, superType){ 
 var prototype = object(superType.prototype); //创建对象
 prototype.constructor = subType; //增强对象
 subType.prototype = prototype; //指定对象
}


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); 
 
 this.age = age; 
} 
inheritPrototype(SubType, SuperType); 
SubType.prototype.sayAge = function(){ 
 alert(this.age); 
};
这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和 isPrototypeOf() 开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

小结

工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。
构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用 new 操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。
原型模式,使用构造函数的 prototype 属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。
寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值