前端开发必须知道的JS之原型和继承


Posted in Javascript onJuly 06, 2010

一. 原型与构造函数

Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型。譬如普通函数:

function F(){ 
alert(F.prototype instanceof Object) //true; 
}

构造函数,也即构造对象。首先了解下通过构造函数实例化对象的过程。

function A(x){ 
this.x=x; 
} 
var obj=new A(1);

实例化obj对象有三步:

1. 创建obj对象:obj=new Object();

2. 将obj的内部__proto__指向构造他的函数A的prototype,同时,obj.constructor===A.prototype.constructor(这个是永远成立的,即使A.prototype不再指向原来的A原型,也就是说:类的实例对象的constructor属性永远指向"构造函数"的prototype.constructor),从而使得obj.constructor.prototype指向A.prototype(obj.constructor.prototype===A.prototype,当A.prototype改变时则不成立,下文有遇到)。obj.constructor.prototype与的内部_proto_是两码事,实例化对象时用的是_proto_,obj是没有prototype属性的,但是有内部的__proto__,通过__proto__来取得原型链上的原型属性和原型方法,FireFox公开了__proto__,可以在FireFox中alert(obj.__proto__);

3. 将obj作为this去调用构造函数A,从而设置成员(即对象属性和对象方法)并初始化。

当这3步完成,这个obj对象就与构造函数A再无联系,这个时候即使构造函数A再加任何成员,都不再影响已经实例化的obj对象了。此时,obj对象具有了x属性,同时具有了构造函数A的原型对象的所有成员,当然,此时该原型对象是没有成员的。

原型对象初始是空的,也就是没有一个成员(即原型属性和原型方法)。可以通过如下方法验证原型对象具有多少成员。

var num=0; 
for(o in A.prototype) { 
alert(o);//alert出原型属性名字 

num++; 
} 
alert("member: " + num);//alert出原型所有成员个数。

但是,一旦定义了原型属性或原型方法,则所有通过该构造函数实例化出来的所有对象,都继承了这些原型属性和原型方法,这是通过内部的_proto_链来实现的。

譬如

A.prototype.say=function(){alert("Hi")};

那所有的A的对象都具有了say方法,这个原型对象的say方法是唯一的副本给大家共享的,而不是每一个对象都有关于say方法的一个副本。

二. 原型与继承

首先,看个简单的继承实现。

function A(x){ 
this.x=x; 
} 
function B(x,y){ 

this.tmpObj=A; 

this.tmpObj(x); 

delete this.tmpObj; 

this.y=y; 
}

第5、6、7行:创建临时属性tmpObj引用构造函数A,然后在B内部执行,执行完后删除。当在B内部执行了this.x=x后(这里的this是B的对象),B当然就拥有了x属性,当然B的x属性和A的x属性两者是独立,所以并不能算严格的继承。第5、6、7行有更简单的实现,就是通过call(apply)方法:A.call(this,x);

这两种方法都有将this传递到A的执行里,this指向的是B的对象,这就是为什么不直接A(x)的原因。这种继承方式即是类继承(js没有类,这里只是指构造函数),虽然继承了A构造对象的所有属性方法,但是不能继承A的原型对象的成员。而要实现这个目的,就是在此基础上再添加原型继承。

通过下面的例子,就能很深入地了解原型,以及原型参与实现的完美继承。(本文核心在此^_^)

function A(x){ 
this.x = x; 
} 
A.prototype.a = "a"; 
function B(x,y){ 

this.y = y; 

A.call(this,x); 
} 
B.prototype.b1 = function(){ 

alert("b1"); 
} 
B.prototype = new A(); 
B.prototype.b2 = function(){ 

alert("b2"); 
} 
B.prototype.constructor = B; 
var obj = new B(1,3);

这个例子讲的就是B继承A。第7行类继承:A.call(this.x);上面已讲过。实现原型继承的是第12行:B.prototype = new A();

就是说把B的原型指向了A的1个实例对象,这个实例对象具有x属性,为undefined,还具有a属性,值为"a"。所以B原型也具有了这2个属性(或者说,B和A建立了原型链,B是A的下级)。而因为方才的类继承,B的实例对象也具有了x属性,也就是说obj对象有2个同名的x属性,此时原型属性x要让位于实例对象属性x,所以obj.x是1,而非undefined。第13行又定义了原型方法b2,所以B原型也具有了b2。虽然第9~11行设置了原型方法b1,但是你会发现第12行执行后,B原型不再具有b1方法,也就是obj.b1是undefined。因为第12行使得B原型指向改变,原来具有b1的原型对象被抛弃,自然就没有b1了。

第12行执行完后,B原型(B.prototype)指向了A的实例对象,而A的实例对象的构造器是构造函数A,所以B.prototype.constructor就是构造对象A了(换句话说,A构造了B的原型)。

alert(B.prototype.constructor)出来后就是"function A(x){...}" 。同样地,obj.constructor也是A构造对象,alert(obj.constructor)出来后就是"function A(x){...}" ,也就是说B.prototype.constructor===obj.constructor(true),但是B.prototype===obj.constructor.prototype(false),因为前者是B的原型,具有成员:x,a,b2,后者是A的原型,具有成员:a。如何修正这个问题呢,就在第16行,将B原型的构造器重新指向了B构造函数,那么B.prototype===obj.constructor.prototype(true),都具有成员:x,a,b2。

如果没有第16行,那是不是obj = new B(1,3)会去调用A构造函数实例化呢?答案是否定的,你会发现obj.y=3,所以仍然是调用的B构造函数实例化的。虽然obj.constructor===A(true),但是对于new B()的行为来说,执行了上面所说的通过构造函数创建实例对象的3个步骤,第一步,创建空对象;第二步,obj.__proto__ === B.prototype,B.prototype是具有x,a,b2成员的,obj.constructor指向了B.prototype.constructor,即构造函数A;第三步,调用的构造函数B去设置和初始化成员,具有了属性x,y。虽然不加16行不影响obj的属性,但如上一段说,却影响obj.constructor和obj.constructor.prototype。所以在使用了原型继承后,要进行修正的操作。

关于第12、16行,总言之,第12行使得B原型继承了A的原型对象的所有成员,但是也使得B的实例对象的构造器的原型指向了A原型,所以要通过第16行修正这个缺陷。

毕了。

Javascript 相关文章推荐
Javascript的一种模块模式
Mar 22 Javascript
javascript,jquery闭包概念分析
Jun 19 Javascript
jquery prop的使用介绍及与attr的区别
Dec 19 Javascript
jquery实现的鼠标拖动排序Li或Table
May 04 Javascript
js在指定位置增加节点函数insertBefore()用法实例
Jan 12 Javascript
如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框
Mar 01 Javascript
JQuery Mobile 弹出式登录框的实现方法
May 28 Javascript
详解使用create-react-app快速构建React开发环境
May 16 Javascript
详解nuxt路由鉴权(express模板)
Nov 21 Javascript
微信小程序如何实现全局重新加载
Jun 05 Javascript
Node对CommonJS的模块规范
Nov 06 Javascript
es6函数之严格模式用法实例分析
Mar 17 Javascript
基于jQuery的的一个隔行变色,鼠标移动变色的小插件
Jul 06 #Javascript
一个基于jquery的图片切换效果
Jul 06 #Javascript
jQuery ajax BUG:object doesn't support this property or method
Jul 06 #Javascript
防止页面被iframe(兼容IE,Firefox火狐)
Jul 04 #Javascript
JavaScript对象、属性、事件手册集合方便查询
Jul 04 #Javascript
JavaScript 内置对象属性及方法集合
Jul 04 #Javascript
js以对象为索引的关联数组
Jul 04 #Javascript
You might like
DOTA2 无惧惊涛骇浪 昆卡大型水友攻略
2020/04/20 DOTA
php网站来路获取代码(针对搜索引擎)
2010/06/08 PHP
php多个字符串替换成同一个的解决方法
2013/06/18 PHP
学习php设计模式 php实现装饰器模式(decorator)
2015/12/07 PHP
php使用timthumb生成缩略图的方法
2016/01/22 PHP
js选取多个或单个元素的实现代码(用class)
2012/08/22 Javascript
jQuery 借助插件Lavalamp实现导航条动态美化效果
2013/09/27 Javascript
js获取IFRAME当前的URL的方法
2013/11/13 Javascript
jquery简单实现外部链接用新窗口打开的方法
2015/05/30 Javascript
jQuery实现按钮只点击一次后就取消点击事件绑定的方法
2015/06/26 Javascript
jquery实现浮动在网页右下角的彩票开奖公告窗口代码
2015/09/04 Javascript
使用JQuery实现Ctrl+Enter提交表单的方法
2015/10/22 Javascript
如何根据百度地图计算出两地之间的驾驶距离(两种语言js和C#)
2015/10/29 Javascript
JS实现图片平面旋转的方法
2016/03/01 Javascript
vuejs+element-ui+laravel5.4上传文件的示例代码
2017/08/12 Javascript
基于twbsPagination.js分页插件使用心得(分享)
2017/10/21 Javascript
jQuery 实现DOM元素拖拽交换位置的实例代码
2020/07/14 jQuery
jQuery实现简单评论区功能
2020/10/26 jQuery
vue 根据选择的月份动态展示日期对应的星期几
2021/02/06 Vue.js
[05:34]2014DOTA2国际邀请赛中国区预选赛精彩TOPPLAY第二弹
2014/06/25 DOTA
[42:04]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#3Secret VS OG第一局
2016/03/03 DOTA
python中使用sys模板和logging模块获取行号和函数名的方法
2014/04/15 Python
python使用多线程不断刷新网页的方法
2015/03/31 Python
python版本的读写锁操作方法
2016/04/25 Python
python实现超简单的视频对象提取功能
2018/06/04 Python
python和node.js生成当前时间戳的示例
2020/09/29 Python
Trip.com香港网站:Ctrip携程旗下,全球最大的网上旅游社之一
2016/08/01 全球购物
香港时装购物网站:ZALORA香港
2017/04/23 全球购物
新西兰床上用品和家居用品购物网站:Adairs
2018/04/27 全球购物
哈萨克斯坦移动和数字技术在线商店:SatelOnline.kz
2020/09/04 全球购物
Collection和Collections的区别
2016/05/02 面试题
投标人法定代表人授权委托书格式
2014/09/28 职场文书
中学生检讨书范文
2014/11/03 职场文书
Golang二维切片初始化的实现
2021/04/08 Golang
详解用Python把PDF转为Word方法总结
2021/04/27 Python
详解Flutter和Dart取消Future的三种方法
2022/04/07 Java/Android