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 相关文章推荐
用YUI做了个标签浏览效果
Feb 20 Javascript
JavaScript 设计模式 富有表现力的Javascript(一)
May 26 Javascript
jquery 回车事件实现代码
Aug 23 Javascript
js实现收缩菜单效果实例代码
Oct 30 Javascript
详解js闭包
Sep 02 Javascript
Javascript基础教程之argument 详解
Jan 18 Javascript
js手动播放图片实现图片轮播效果
Sep 17 Javascript
解析javascript图片懒加载与预加载的分析总结
Oct 27 Javascript
使用MUI框架模拟手机端的下拉刷新和上拉加载功能
Sep 04 Javascript
ES6基础之字符串和函数的拓展详解
Aug 22 Javascript
vue 返回上一页,页面样式错乱的解决
Nov 14 Javascript
jQuery实现朋友圈查看图片
Sep 11 jQuery
基于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
PHP Undefined index报错的修复方法
2011/07/17 PHP
php获取英文姓名首字母的方法
2015/07/13 PHP
PHP pear安装配置教程
2016/05/14 PHP
基于jquery的返回顶部效果(兼容IE6)
2011/01/17 Javascript
jquery入门—访问DOM对象方法
2013/01/07 Javascript
jQuery基本选择器选择元素使用介绍
2013/04/18 Javascript
JavaScript获取和设置CheckBox状态的简单方法
2013/07/05 Javascript
jquery ztree实现下拉树形框使用到了json数据
2014/05/14 Javascript
javascript 实现 原路返回
2015/01/21 Javascript
JS函数this的用法实例分析
2015/02/05 Javascript
javascript实现数组去重的多种方法
2016/03/14 Javascript
JS 清除字符串数组中,重复元素的实现方法
2016/05/24 Javascript
bootstrap table小案例
2016/10/21 Javascript
jQuery实现联动下拉列表查询框
2017/01/04 Javascript
vue实现个人信息查看和密码修改功能
2018/05/06 Javascript
JavaScript 复制对象与Object.assign方法无法实现深复制
2018/11/02 Javascript
Vue.js + Nuxt.js 项目中使用 Vee-validate 表单校验
2019/04/22 Javascript
Vue侦测相关api的实现方法
2019/05/22 Javascript
Vue设置长时间未操作登录自动到期返回登录页
2020/01/22 Javascript
微信小程序 bindtap 传参的实例代码
2020/02/21 Javascript
[41:37]DOTA2北京网鱼队选拔赛——冲击职业之路
2015/04/13 DOTA
Python实现比较两个列表(list)范围
2015/06/12 Python
详谈在flask中使用jsonify和json.dumps的区别
2018/03/26 Python
django orm 通过related_name反向查询的方法
2018/12/15 Python
对python 中class与变量的使用方法详解
2019/06/26 Python
Django实现文件上传和下载功能
2019/10/06 Python
Python计算矩阵的和积的实例详解
2020/09/10 Python
细说NumPy数组的四种乘法的使用
2020/12/18 Python
教你一分钟在win10终端成功安装Pytorch的方法步骤
2021/01/28 Python
美国隐形眼镜销售网站:ContactsDirect
2017/10/28 全球购物
美国亚马逊旗下时尚女装网店:SHOPBOP(支持中文)
2020/10/17 全球购物
LVMH旗下最大的奢侈品网站平台:24S
2020/05/24 全球购物
什么是Smart Navigation?
2016/07/03 面试题
应届生求职简历的自我评价怎么写
2013/10/23 职场文书
详解redis分布式锁的这些坑
2021/05/19 Redis