JS Pro-深入面向对象的程序设计之继承的详解


Posted in Javascript onMay 07, 2013

原型链(prototype chaining):

利用原型来继承属性和方法。回顾一下构造函数(constructor),原型对象(prototype)和实例(instance)的关系。每一个构造函数都有一个prototype属性,该属性指向一个prototype对象;prototype对象也有constructor属性,指向该函数;而实例也有一个内部指针(__proto__)指向这个prototype对象。如果这个prototype对象是另外一个对象的实例会是怎样的呢?这样该prototype对象就包含一个指向另一个类型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。

JS的继承很简单,就是把子类的prototype设为父类的一个(实例化)对象

function SuperType(){
    this.property = true;
}SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());   //true

最终的结果:instance的__proto__指向SubType.prototype对象,而SubType.prototype对象的__proto__属性又指向SuperType.prototype对象。getSuperValue()是一个方法,所以仍然存在于原型中,而property是一个实例属性,所以现在存在于SubType.prototype这个实例中。  instance.constructor现在指向的是SuperType,这是由于SubType.prototype指向SuperType.prototype,而SuperType.prototype的constructor属性指向SuperType函数,所以instance.constructor指向SuperType。

默认情况下,所有引用类型都继承Object。这是因为所有函数的原型对象,默认都是Object的一个实例,所以内部prototype(__proto__)指向Object.Prototype。

原型和实例的关系:可以使用2种方法来确定原型与实例之间的关系。
- instancef操作符:使用该操作符来测试实例与原型链中出现过的构造函数,都会返回true

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

给子类添加方法的注意点:我们有的时候会给子类添加方法,或者是重写父类的某些方法。这个时候就要注意,这些方法必须在继承后再定义。以下的例子里,SubType在继承SuperType后,我们给它添加了新的方法getSubValue(),而且重写了getSuperValue()方法。对于后者,只有SubType的实例才会使用重写的方法,SuperType的实例还是会使用原有的getSuperValue()方法。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){

return this.property;
};
function SubType(){

t his.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
//new method
SubType.prototype.getSubValue = function (){

return this.subproperty;
};
//override existing method
SubType.prototype.getSuperValue = function (){

return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false

另外一个需要注意的是,通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链。如下面的代码,在SubType继承SuperType以后,使用对象字面量给原型添加方法,但这样做,会重写SubType原型,重写后的SubType.prototype包含的是一个Object的实例,从而也切断了与SuperType的关系。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){

return this.property;
};
function SubType(){

this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
//try to add new methods - this nullifies the previous line
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(){
}
//inherit from 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”

在SuperType构造函数中,我们定义了一个colors数组,每一个SuperType实例都会拥有它自己的这个colors数组。但是当SubType使用原型链继承SuperType以后,SubType.prototype变成SuperType的一个实例,因此它拥有自己的colors属性,也就是说SubType.prototype.colors属性。所以,当创建SubType实例的时候,所有实例都共享这一属性了。如上面的代码所示。

第二个问题就是:在创建子类的实例时,不能向超类的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类的构造函数传递参数。由于这些问题,我们不会单独使用原型链。

--------------------------------------------------------------------------------

借用构造函数(Contructor stealing):
为了解决上述问题,开发人员发明了一种叫做借用构造函数的技术。这种技术的思路就是:在子类型构造函数的内部调用超类型构造函数。(函数,只不过是在特定环境中执行代码的对象?)我们可以通过使用apply()或call()方法在新创建的对象上执行构造函数。

function SuperType(){
this.colors = [“red”, “blue”, “green”];
}
function SubType(){

//inherit from 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”

我们在SubType里使用call()方法调用SuperType的构造函数,实际上就是在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果就是每个SubType实例都具有自己的colors属性的副本。

传递参数:使用借用构造函数方法的一个很大的好处在于就是,我们可以从子类的构造函数传递参数到父类的构造函数中。

function SuperType(name){
this.name = name;
}
function SubType(){

//inherit from SuperType passing in an argument

SuperType.call(this, “Nicholas”);

//instance property

this.age = 29;
}
var instance = new SubType();
alert(instance.name); //”Nicholas”;
alert(instance.age); //29

新的SuperType构造函数新增了一个参数name,我们在call SuperType的同时,往SuperType传递参数"Nicholas"。为了不让超类型的构造函数重写子类型的属性,可以在调用超类型构造函数后再定义子类的属性。

借用构造函数的问题:方法都在构造函数中定义,无法复用。而且在超类型的原型中定义的方法,对子类型而言是不可见的。结果所有类型都只能使用构造函数模式。

--------------------------------------------------------------------------------

组合继承:
结合原型链及借用构造函数各自的优点的一种继承模式。使用原型链继承原型属性及方法,使用借用构造函数来继承实例属性。如下面例子,我们使用call()方法调用SuperType的构造函数(每个SubType实例都拥有自己的name和colors属性,以及SubType的age属性);然后再把SuperType实例赋值给SubType的原型,使其继承SuperType的sayName()方法(每个实例都共用这个方法)。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    //inherit properties
    SuperType.call(this, name);
    this.age = age;
}
//inherit methods
SubType.prototype = new SuperType();
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

原型式继承(Prototypal Inheritance):
function object(o){
function F(){}

F.prototype = o;

return new F();
}
--------------------------------------------------------------------------------

寄生式继承(Parasitic Inheritance):

缺点同构造函数

Javascript 相关文章推荐
提高代码性能技巧谈—以创建千行表格为例
Jul 01 Javascript
jQuery EasyUI API 中文文档 - NumberSpinner数值微调器使用介绍
Oct 21 Javascript
用RadioButten或CheckBox实现div的显示与隐藏
Sep 21 Javascript
jquery删除提示框弹出是否删除对话框
Jan 07 Javascript
ie下$.getJSON出现问题的解决方法
Feb 12 Javascript
jQuery学习笔记之jQuery.fn.init()的参数分析
Jun 09 Javascript
使用jQuery和Bootstrap实现多层、自适应模态窗口
Dec 22 Javascript
15个jquery常用方法、小技巧分享
Jan 13 Javascript
使用PHP+JavaScript将HTML页面转换为图片的实例分享
Apr 18 Javascript
详解Vue用自定义指令完成一个下拉菜单(select组件)
Oct 31 Javascript
JS中判断某个字符串是否包含另一个字符串的五种方法
May 03 Javascript
vue cli3适配所有端方案的实现
Apr 13 Javascript
基于JavaScript实现继承机制之原型链(prototype chaining)的详解
May 07 #Javascript
基于JavaScript实现继承机制之构造函数+原型链混合方式的使用详解
May 07 #Javascript
使用javascript:将其它类型值转换成布尔类型值的解决方法详解
May 07 #Javascript
JQuery+CSS提示框实现思路及代码(纯手工打造)
May 07 #Javascript
基于IE下ul li 互相嵌套时的bug,排查,解决过程以及心得介绍
May 07 #Javascript
解决javascript:window.close()在chrome,Firefox下失效的问题
May 07 #Javascript
jQuery的slideToggle方法实例
May 07 #Javascript
You might like
2.PHP入门
2006/10/09 PHP
PHP中__get()和__set()的用法实例详解
2013/06/04 PHP
php计算整个目录大小的方法
2015/06/19 PHP
详解PHP5.6.30与Apache2.4.x配置
2017/06/02 PHP
jQuery下通过replace字符串替换实现大小图片切换
2012/05/22 Javascript
一个JavaScript函数把URL参数解析成Json对象
2014/09/24 Javascript
javascript关于open.window子页面执行完成后刷新父页面的问题分析
2015/04/27 Javascript
jQuery实例—选项卡的简单实现(js源码和jQuery)
2016/06/14 Javascript
D3.js实现直方图的方法详解
2016/09/25 Javascript
JavaScript使用delete删除数组元素用法示例【数组长度不变】
2017/01/17 Javascript
bootstrap中的 form表单属性role="form"的作用详解
2017/01/20 Javascript
JavaScript实现的仿新浪微博原生态输入字数即时检查功能【兼容IE6】
2017/09/26 Javascript
layer实现关闭弹出层刷新父界面功能详解
2017/11/15 Javascript
浅谈Javascript常用正则表达式应用
2019/03/08 Javascript
[31:00]2014 DOTA2华西杯精英邀请赛5 24 NewBee VS iG
2014/05/25 DOTA
[05:31]DOTA2英雄梦之声_第04期_光之守卫
2014/06/23 DOTA
Python获取服务器信息的最简单实现方法
2015/03/05 Python
Python读取键盘输入的2种方法
2015/06/16 Python
常见python正则用法的简单实例
2016/06/21 Python
如何利用Fabric自动化你的任务
2016/10/20 Python
python基于twisted框架编写简单聊天室
2018/01/02 Python
pytorch中交叉熵损失(nn.CrossEntropyLoss())的计算过程详解
2020/01/02 Python
使用tensorflow显示pb模型的所有网络结点方式
2020/01/23 Python
PySide2出现“ImportError: DLL load failed: 找不到指定的模块”的问题及解决方法
2020/06/10 Python
澳大利亚足球鞋和服装购物网站:Ultra Football
2018/10/11 全球购物
西班牙在线光学:Visual-Click
2020/06/22 全球购物
违反学校规定检讨书
2014/01/18 职场文书
酒店销售经理岗位职责
2014/01/31 职场文书
投资意向书
2014/07/30 职场文书
2014年科室工作总结范文
2014/12/19 职场文书
布达拉宫导游词
2015/02/02 职场文书
2015年出纳个人工作总结
2015/04/02 职场文书
会议主持人开场白台词
2015/05/28 职场文书
永不妥协观后感
2015/06/10 职场文书
薪资证明范本
2015/06/19 职场文书
导游词之无锡东林书院
2019/12/11 职场文书