原文地址:http://blog.endlesscode.com/2010/01/23/javascript-prototype-chain/
原型的含义是指:如果构造器有个原型对象A,则由该构造器创建的实例(Object Instance)都必然复制于A。““在JavaScript中,对象实例(Object Instance)并没有原型,而构造器(Constructor)有原型,属性’<构造器>.prototype’指向原型。对象只有“构 造自某个原型”的问题,并不存在“持有(或拥有)某个原型”的问题。””如何理解这一句话?
代码1:
function myFunc() { var name = "stephenchan"; var age = 23; function code() { alert("Hello World!"); }; } var obj = new myFunc(); //输出undefined,对象实例没有原型 alert(obj.prototype); //输出myFunc的函数代码,obj由myFunc构造出来的 alert(obj.constructor); //输出true alert(obj.constructor == myFunc); //输出[object Object],说明myFunc的原型是一个对象 alert(myFunc.prototype); //输出function Function() { [native code] },[native code]的意思是JavaScript引擎的内置函数 alert(myFunc.constructor); //输出true,函数原型的构造器默认是该函数 alert(myFunc.prototype.constructor == myFunc);
构造器与函数的概念是一致的,即代码1中,myFunc就是一个构造器,因为通过new myFunc()就可以构造出一个对象实例了。因此,”alert(obj.prototype)”输出undefined说明了对象实例是没有原型的,”alert(myFunc.prototype)”输出[object Object]说明了构造器有原型,而“obj.constructor==myFunc”返回true说明obj的构造器是myFunc。
原型其实也是一个对象实例。再强调一下原型的含义是:如果构造器有个原型对象A,则由该构造器创建的实例 (Object Instance)都必然复制于A,而且采用的读遍历机制复制的。读遍历复制的意思是:仅当写某个实例的成员时,将成员的信息复制到实例映像中。即当构造 一个新的对象时,新对象里面的属性指向的是原型中的属性,读取对象实例的属性时,获取的是原型对象的属性值。而当对象实例对一个属性进行写操作时,才会将 属性写到新对象实例的属性列表中。
图1 JavaScript使用读遍历机制实现的原型继承
代码2:
Object.prototype.value = "abc"; var obj1 = new Object(); var obj2 = new Object(); obj2.value = 10; //输出abc,读取的是原型Object中的value alert(obj1.value); //输出10,读取的是obj2成员列表中的value alert(obj2.value); //删除obj2中的value,即在obj2的成员列表中将value删除掉 delete obj2.value; //输出abc,读取的是原型Object中的value alert(obj2.value);
图1是对代码2的描述,说明读遍历机制是如何在成员列表以至原型中管理对象成员的。只有对属性进行第一次写操作的时候,才会在对象的成员列表中添加 该属性的记录。当obj1和obj2通过new来构造出来的时候,仍然是一个指向原型的引用,在操作过程中也没有与原型相同大小的对象实例创建出来。这样 的读遍历就避免在创建新对象实例时可能的大量内存分配。当obj2.value属性被赋值为10的时候,obj2则在其成员表中添加了一个value成 员,并赋值为10,这个成员表就是记录了obj2中发生了修改的成员名、值与类型。这张表是否与原型一致并不重要,只需要遵循两条规则:(1)保证在读取 时被首先访问到。(2)如果在对象中没有指定的属性,则尝试遍历对象的整个原型链,直到原型为空或找到该属性。代码2中的delete操作是将obj2成 员表中的value删除了,因此在读取obj2的value属性的时候就遍历到Object中读取。
函数的原型总是一个标准的、系统内置的Object()构造器的实例,不过该实例创建后constructor属性总先被赋值为当前的函数。
代码3:
function MyObject() { } //显示true,表明原型的构造器总是指向函数自身的 alert(MyObject.prototype.constructor == MyObject); //删除该成员 delete MyObject.prototype.constructor; //删除操作使该成员指向了父代类原型中的值 //均显示为true alert(MyObject.prototype.constructor == Object); alert(MyObject.prototype.constructor == new Object().constructor);
从代码3中可以看出,MyObject.prototype其实与一个普通对象”new Object()”并没有本质的区别,只是在创建时将constructor赋值为当前函数MyObject。然后,当一个函数的prototype有意 义之后,它就摇身一变成了一个“构造器”,这时,如果用户试图用new运算符创建它的实例时,那么引擎就会再构造一个新的对象,并使这个新对象的原型链接 向这个prototype属性就可以了。因此,函数与构造器并没有明显的界限。
一个构造器产生的实例,其constructor属性默认总是指向该构造器,而究其根源,则在于构造器(函数)的原型的constructor属性指向了构造器本身。
代码4:
function MyObject() {
}
var obj = new MyObject();
//输出为true,默认指向构造器
alert(obj.constructor == MyObject);
//输出为true,原型的构造器指向该构造器
alert(MyObject.prototype.constructor == MyObject);
由此可见,JavaScript事实上已经为构造器维护了原型属性,因此我们可以通过实例的constructor属性来找到构造器,并进而找到它 的原型“obj.constructor.prototype”。但是,如果我们把构造器的原型修改了的话,会出现什么情况呢?如代码5,我们把 MyObjectEx的原型修改了。
代码5:
}
function MyObjectEx() {
}
MyObjectEx.prototype = new MyObject();
var obj1 = new MyObject();
var obj2 = new MyObjectEx();
alert(obj1.constructor == obj2.constructor); //true
alert(MyObjectEx.prototype.constructor == MyObject.prototype.constructor); //true
在代码5中,obj1和obj2是由不同的两个构造器产生的实例,分别是MyObject和MyObjectEx。然而,我们看到,代码5中的两个alert都会输出true,即是说,由两个不相同的构造器产生的实例(代码5中的MyObject和MyObjectEx),它们的constructor属性却指向了相同的构造器, 是不是很诡异?这个正确是体现了原型继承中出出现的“原型复制”了。要注意,MyObjectEx的原型是由MyObject构造出来的对象实例,即 obj1和obj2都是从MyObject原型中复制出来的对象,因此它们的constructor指向的都是MyObject。那么怎么解决这个问题?
代码6:
this.constructor = arguments.callee; //arguments.callee为MyObject,正确维护constructor,以便回溯外部原型链
}
MyObject.prototype = new Object(); //人为构建外部原型链
function MyObjectEx() {
this.constructor = arguments.callee; //正确维护constructor,以便回溯外部原型链
}
MyObjectEx.prototype = new MyObject(); //人为构建外部原型链
obj1 = new MyObjectEx();
obj2 = new MyObjectEx();
代码6与代码5中的主要区别就是在于,在MyObjectEx的初始化中正确地维护了constructor属性,使当前的constructor属性指向了调用的构造器。代码6所描述的继承关系如图2:
图2 构造器原型链与内部原型链
其中有[proto]属性中一个对象的私有属性,用于正确维护对象的内部原型链,在Firefox中可以通过[__proto__]来访问,这个后 面再讨论。我们可以看到MyObjectEx的构造器是MyObject的对象实例,而MyObject的构造器是Object的对象实例。
接代码6:
alert(obj.constructor === MyObject); //true
alert(obj1.constructor === MyObjectEx); //true
alert(obj.constructor === obj1.constructor); //false
可以看到,obj和obj1从不同的构造器产生的实例,其constructor属性已经能够正确地指向相应的构造器,这个是由于在对象实例初始化 的时候的赋值语句”this.constructor = arguments.callee;”。你可能会疑问为什么不采用下面这种方式来实现:
MyObjectEx.prototype.constructor = MyObjectEx;
这样虽然能使obj1和obj2的constructor属性正确地指向了MyObjectEx,但是,这样同时也使得MyObjectEx的原型 对象(MyObject构造的实例)的constructor属性没法往父代原型追溯。因为当MyObjectEx的原型对象想通过 constructor属性来获取到MyObject构造器时,会发现获取到的是MyObjectEx的构造器,而不是期待的MyObject的构造器。
我们可以通过下面的语句来验证代码6是不是的确是如图2的关系链:
alert(obj1.constructor === MyObjectEx); //true
alert(MyObjectEx.prototype instanceof MyObject); //true
alert(MyObjectEx.prototype.constructor === MyObject); //true
alert(MyObject.prototype instanceof Object); //true
alert(MyObject.prototype.constructor === Object); //true
alert(obj1.constructor.prototype.constructor.prototype.constructor === Object); //true,完成了所有的回溯
好了,刚才上面提到了有一个不可访问的属性[proto],这个属性是JavaScript引擎内部维护的原型链属性,这个属性在Firefox里 面可以通过[__proto__]来访问的,一般情况下,[proto]属性指向的和prototype属性一样,指向的都是原型对象,两个有什么不同后 面会有讲述。
alert(obj.__proto__ instanceof Object);
alert(obj1.__proto__ instanceof MyObject);
alert(obj2.__proto__ instanceof MyObject);
这个[proto]属性是JavaScript内部维护的,外部是不可访问的,由这个属性所维护的原型链为内部原型链,与由prototype和constructor维护的外部原型链。那么这两条原型链有什么区别呢?简单来说就是,通过prototype和constructor来维护的外部原型链是开发人员自己代码中回溯时用到的,而通过[proto]维护的内部原型链是JavaScript原型继承机制实现所需要的。 具体来说,外部原型链就是做这种 事:”alert(obj1.constructor.prototype.constructor.prototype.constructor === Object);”,也就是说当我们开发人员想要自己去回溯整个原型继承的结构链时,也只会在我们开发人员写代码时才出现通过prototype和 constructor来访问外部原型链。而内部原型链,这个比较有意思,在[图1 JavaScript使用读遍历机制实现的原型继承],我们看到,当我们访问一个对象实例的属性时,它如果发现在其成员列表中没有该属性,即会去访问原型 的成员列表,把原型的默认值读取出来,也就是说,这个在原型链中回溯来查询成员属性的过程,只会在内部原型链中进行,这个过程是由JavaScript引 擎自己去维护的,开发人员没法干涉。来看看代码,我觉得这个还是相当有意思的:
接代码6:
alert(obj1.__proto__ instanceof MyObject); //true
alert(obj2.__proto__ instanceof MyObject); //true
//按照上面所说的,在MyObjectEx的原型上添加了value的属性,那么在访问obj1和obj2的value属性时便会往原型中查找
MyObjectEx.prototype.value = "Hello World!";
//这里正确地输出"Hello World!"
alert(obj1.value);
//在此时,obj1和obj2都构造之后,我把原来的MyObjectEx的原型换了,变成MyObjectEx2
function MyObjectEx2() {}
MyObjectEx.prototype = new MyObjectEx2();
//这句究竟会输出什么呢?[Referece Error]还是?
alert(obj1.value);
最后的1个alert输出的”Hello World!”,有意思吧。即使我在上面把MyObjectEx的原型对象改变成新的MyObjectEx2,但是在obj1和obj2中的 [proto]属性依然指向的是原来的MyObject构造的对象实例,也就是说内部访问属性时是通过[proto]来回溯原型链的,而不是通过 prototype的(而且对象实例也没有prototype属性),这个就是内部原型链体现的威力。
参考:
- JavaScript对象真经
- 《JavaScript语言精髓与编程实践》
发表评论
-
基于脚本的动画的计时控制(“requestAnimationFrame”)(转载)
2014-03-04 19:12 1054Internet Explorer 10 和使 ... -
IE11开发人员工具:UI响应工具详解
2014-02-27 18:33 950我讨厌debug,相信也没多少开发者会喜欢。但是当代码出 ... -
IE11开发人员工具:内存分析工具详解
2014-02-27 18:32 1466上篇我们跟大家介绍 ... -
E6与location.hash和Ajax历史记录 (转载)
2014-02-26 12:23 563为了在IE6中改变hash来保留历史记录实现ajax的前进 ... -
MIME Types(转载)
2013-12-31 10:20 652MIME Types - Complete List ... -
iframe历史记录问题(转载)
2013-10-17 10:21 1353在做页面统计的时候 ... -
前端类库精选(转)
2013-05-11 00:57 0优秀的前端类库,自己平时遇见了,这里Mark一下。 1、m ... -
10个chrome console实用小技巧(转)
2013-05-09 10:56 10751. 基本输出 让我们先从最常见的console.l ... -
CSS3那些不为人知的高级属性(转)
2013-04-19 13:35 963原文:CSS的未来:一些 ... -
JavaScript 时间、格式、转换及Date对象总结(转)
2013-04-10 14:49 726悲剧的遇到问题,从前台得到时间,“Tue Jan 29 16 ... -
如何制作一个可及性强(accessible)的网页弹框(转载)
2013-04-02 16:18 813英文原文:Making an accessib ... -
JavaScript MVC js也mvc(转载)
2013-03-16 23:59 694JavaScript MVC 中文:http://blog ... -
SUBLIME TEXT 2 设置文件详解
2012-12-27 11:21 1066Sublime Text 2是那种让人会一眼就爱上的编辑 ... -
两个按位非操作与Math.floor操作(译)
2012-12-10 18:17 961位操作符在我们编码过程中是容易被遗忘的,可能更多的源于我们 ... -
img中src为空的影响
2012-11-26 23:32 0这是我们经常能遇到的代码,可以直接用html标签或者Java ... -
IE6下position定位子元素溢出,父元素被撑开的解决思路。(转)
2012-11-13 18:04 1652在一些被常规的页面布局当中,我们常常需要通过positi ... -
chrome developer tool 调试技巧(转)
2012-11-12 13:16 873这篇文章是根据目前 chrome 稳定版(19.0.10 ... -
你清楚jquery是如何清除ajax缓存的吗?(转)
2012-11-11 11:19 1061大家都知道万恶的IE在ajax中往往只读取第一次ajax ... -
是时候使用JavaScript严谨模式(Strict Mode)提升团队开发效率 In JavaScript(转)
2012-11-10 23:33 729随着WebApp突飞猛进的发展,Javascript写的 ... -
Javascript基础
2012-11-10 23:25 0原文:http://bonsaiden.githu ...
相关推荐
### 浅析JavaScript原型继承机制 #### 一、引言 JavaScript作为一种动态语言,其对象模型与传统的面向对象编程语言有所不同。在JavaScript中,并没有直接提供类的概念,而是通过原型来实现继承。本文将深入探讨...
在JavaScript中,继承主要通过原型链实现。首先,我们需要区分几个重要概念:函数(Function),对象(Object),构造器(Constructor),以及实例(Instance)。在JavaScript中,函数实际上也是一种特殊的对象,它...
浅析JavaScript实现基于原型对象的“继承” 本文旨在对JavaScript实现基于原型对象的“继承”进行深入分析,并与基于类的继承进行比较。通过对JavaScript的原型继承机制的介绍和实例分析,提出一个改进的“寄生组合...
一、什么是原型链? 简单回顾下构造函数,原型和实例的关系: 每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针. ...
总之,理解JavaScript原型继承的陷阱对于编写可维护和高效的代码至关重要。在设计对象模型时,应谨慎处理引用类型的属性,以防止不必要的共享状态,同时利用原型链来优化方法的继承。通过正确地划分数据属性和方法,...
"浅析JavaScript的安全性和执行效率" JavaScript 作为一门广泛应用于 Web 客户端的编程语言,其安全性和执行效率是非常重要的考虑因素。在这个文件中,我们将浅析 JavaScript 的安全性和执行效率,并讨论一些相关的...
"浅析JavaScript在动态网页设计中的应用" 本文主要讨论了JavaScript在动态网页设计中的应用。首先,文章介绍了当前信息时代的背景,指出传统的静态网页已经不能满足人们的需求,动态网页设计成为了当前网页设计的...
`isPrototypeOf(object)`方法则用来判断传入的对象是否是另一个对象的原型,这在处理继承关系和原型链时很有帮助。 `toLocaleString()`返回根据当前地区设置的字符串表示,而`toString()`和`valueOf()`则通常用来...
深化浅析JavaScript中的constructor_ constructor 属性是 JavaScript 中的一种特殊属性,它返回对创建此对象的数组函数的引用。下面我们来深入浅析 JavaScript 中的 constructor。 constructor 属性是一个非标准...
在探讨JavaScript编程时,类型和对象是两个基本且核心的概念。理解它们之间的联系对于编写高效且可复用的代码至关重要。JavaScript是一种基于对象的脚本语言,意味着它几乎所有的元素都可以被视为对象。但是,类型和...
JavaScript的继承主要通过原型链(Prototype Chain)和构造函数的原型属性(__proto__)来实现。通过设置一个对象的`__proto__`指向另一个对象,就可以让第一个对象继承第二个对象的属性和方法。另一种常见的继承...
标题中的“NativeJS随记 - 浅析JavaScript Events”表明这篇博客主要讨论的是JavaScript中的事件处理机制。JavaScript事件是Web开发中的重要组成部分,它允许我们响应用户的交互或浏览器的内部变化。在这里,我们将...
7. **函数的其他特性**:JavaScript函数还支持闭包、作用域、原型链等高级特性。闭包允许函数访问并操作其外部作用域的变量,即使在其外部作用域已经销毁的情况下。作用域决定了变量的可见性和生命周期,而原型链则...
"浅析JavaScript MVC框架在系统开发中的应用" JavaScript MVC 框架作为一种常用的模式,在系统开发中的应用具有重要的意义。通过借助MVC架构的优势,大大的提高了系统运行的稳定性。 JavaScript MVC 框架包括多种...
浅析JavaScript MVC框架在Web开发中的应用 JavaScript MVC框架是当前Web开发中的热门话题之一,MVC全称为Model-View-Controller,分别对应模型、视图和控制器三个部分。该框架的出现解决了传统的JavaScript开发中...
浅析JavaScript技术与A5系统功能实现 本文主要讨论JavaScript技术在A5系统功能实现中的应用,通过对A5系统的开发研究成果为例,讲述JavaScript技术如何实现网页内按钮的具体功能实现、新建功能网页在原网页内嵌展 ...