前端开发必须知道的JS(一) 原型和继承

原型和闭包是Js语言的难点,此文主要讲原型及原型实现的继承,在(二)中会讲下闭包,希望对大家有所帮助。若有疑问或不正之处,欢迎提出指正和讨论。

一、原型与构造函数

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方法的一个副本。

二. 原型与继承

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


以下为引用的内容:

1 function A(x){
2   this.x=x;
3 } 

4  function B(x,y){
5   this.tmpObj=A;
6   this.tmpObj(x);
7   delete this.tmpObj;
8   this.y=y;
9 }

第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的原型对象的成员。而要实现这个目的,就是在此基础上再添加原型继承。

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


以下为引用的内容:

 1 function A(x){
2   this.x = x;
3 }
4 A.prototype.a = "a";
5 function B(x,y){
6   this.y = y;
7   A.call(this,x);
8 }
9 B.prototype.b1 = function(){
10   alert("b1");
11 }
12 B.prototype = new A();
13 B.prototype.b2 = function(){
14   alert("b2");
15 }
16 B.prototype.constructor = B;
17 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行修正这个缺陷。

毕了。

时间: 2016-06-09

前端开发必须知道的JS(一) 原型和继承的相关文章

[你必须知道的.NET]第三十五回,判断dll是debug还是release,这是个问题

问题的提出 晚上翻着群里的聊天,发现一个有趣的问题:如何通过编码方式来判断一个dll或者exe为debug build还是release build?由于没有太多的讨论,所以我只好自己找点儿办法,试图解决这个问题,为夜生活带点刺激.于是,便有了本文的探索和分析. 当然,为了充分的调动起大家的主意,省去不必要的google操作,我觉得有必要对Debug和Release两种模式的异同进行一点提纲挈领式的分析,从而为接下来的解决方案打好基础. Debug & Release 我们应用Visual St

iOS程序员必须知道的Android要点

iOS程序员必须知道的Android要点 2014/05/06 | 分类: ANDROID, 开发 | 0 条评论 | 标签: ANDROID 本文由 伯乐在线 - chris 翻译自 objc.欢迎加入Android小组.转载请参见文章末尾处的要求. 本博客英文原文副本 http://blog.csdn.net/opengl_es/article/details/25243257 在移动应用飞速发展的今天,APP只针对IOS平台进行开发已经不够了,如今Android在移动设备占有近80%的市场

Canvas性能技巧:必须知道的Canvas性能技巧

文章简介:你还在抱怨自己写的canvas demo徘徊在10帧以下吗?你还在烦恼打开自己写的应用就听见CUP风扇转吗?你正在写一个javascript Canvas库吗?那么下面九点就是你必须知道的! 你还在抱怨自己写的canvas demo徘徊在10帧以下吗?你还在烦恼打开自己写的应用就听见CUP风扇转吗?你正在写一个javascript Canvas库吗?那么下面九点就是你必须知道的! 一.预渲染错误代码:       var canvas = document.getElementById

你必须知道的.NET

[你必须知道的.NET]第三十五回,判断dll是debug还是release,这 [你必须知道的.NET]第三十四回,object成员,不见了! [你必须知道的.NET]第三十一回,深入.NET 4.0之,从"新"展望 [你必须知道的.NET]第三十三回,深入.NET 4.0之,Lazy<T> [你必须知道的.NET]第三十二回,,深入.NET 4.0之,Tuple一二 [你必须知道的.NET]第三十回:.NET十年(下) [你必须知道的.NET]第二十九回:.NET十年(

[你必须知道的.NET]第二十七回:interface到底继承于object吗?

说在,开篇之前 在.NET世界里,我们常常听到的一句话莫过于"System.Object是一切类型的根,是所有类型的父类",以至于我在<你必须知道的.NET>8.1节 以"万物归宗:System.Object"这样的title为System.Object授予至高荣誉.所以,基于这样的观点就有了下面这句"接口是否也继承于System.Object?",事实上这正是今天在技术群里小小讨论的一个插曲. 1 缘起 在.NET世界里,我们常常听

[你必须知道的.NET]第二十四回:认识元数据和IL(上)

说在,开篇之前 很早就有说说Metadata(元数据)和IL(中间语言)的想法了,一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀,虽然来得没有<第一回:恩怨情仇:is和as>那么迅速,但是Metadata和IL却是绝对重量级的内容,值得我们在任何时间关注,本文就是开始. 1 引言 你可曾想到,我们的C#代码,编译之后究竟为何物?你可曾认知,我们的可执行程序,运行之时的轨迹究竟为哪般?那么,本文通过对Metadata(元数据)和IL(Intermediate Language, 中间语

[你必须知道的.NET]第二十二回:字符串驻留(上)---带着问题思考

走钢丝的人,在刺激中体验快感.带着问题思考,在问题上迸发火花. 或者给问题以答案,或者给答案以问题,你可能永远无法看清全部,但是总能从一点突破很多.事实的关键就在于面对问题,我该如何思考? String Interning(字符串驻留)就是这样一个值得思考的话题,带着问题思考,我们至少要理清以下几个问题: 什么是string? 什么是字符串驻留? 字符串驻留的运行机制及执行过程? 字符串驻留的其他问题? 带着几个问号,你必须知道的.NET,继续更多体验. 1 带着问题? 带着问题思考,是技术探索

[你必须知道的.NET]第二十回:学习方法论

说在,开篇之前 本文,源自我回答刚毕业朋友关于.NET学习疑惑的回复邮件. 本文,其实早计划在<你必须知道的.NET>写作之初的后记部分,但是因为个中原因未能如愿,算是补上本书的遗憾之一. 本文,作为[<你必须知道的.NET>]系列的第20回,预示着这个系列将开始新的征程,算是[你必须知道的.NET]2.0的开始. 本文,作为一个非技术篇章,加塞儿到<你必须知道的.NET>队伍中,我想至少因为回答了以下几个必须知道的非技术问题:.NET应该学习什么? .NET应该如何学

[你必须知道的.NET]第十四回:认识IL代码---从开始到现在

本文将介绍以下内容: ·IL代码分析方法 ·IL命令解析 ·.NET学习方法论 1.引言 自从『你必须知道.NET』系列开篇以来,受到大家很多的关注和支持,给予了anytao巨大的鼓励和动力.俱往昔,我发现很多的园友都把目光和焦点注意在如何理解IL代码这个问题上.对我来说,这真是个莫大的好消息,因为很明显我们的思路慢慢的从应用向底层发生着转变,技巧性的东西是一个方面的积累,底层的探索在我认为也是必不可少的修炼.如果我们选择了来关注这项修炼,那么我们就应该选择如何来着手这项修炼,首先关注anyta