//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function CF(q1, q2){
this.q1=q1;
this.q2=q2;
}
CF.P1="P1 in CF";
CF.P2="P2 in CF";
function Cfp(){
this.CFP1="CFP1 in Cfp";
}
CF.prototype=new Cfp();
var cf1=new CF("aaa", "bbb");
document.write(cf1.CFP1 + "<br />"); //result: CFP1 in Cfp
document.write(cf1.q1 + "<br />"); //result: aaa
document.write(cf1.q2 + "<br />"); //result: bbb
本地属性与继承属性
对象通过隐式Prototype链能够实现属性和方法的继承,但prototype也是一个普通对象,就是说它是一个普通的实例化的对象,而不是纯粹抽象的数据结构描述。所以就有了这个本地属性与继承属性的问题。
首先看一下设置对象属性时的处理过程。JS定义了一组attribute,用来描述对象的属性property,以表明属性property是否可以在JavaScript代码中设值、被for in枚举等。
obj.propName=value的赋值语句处理步骤如下:
1. 如果propName的attribute设置为不能设值,则返回
2. 如果obj.propName不存在,则为obj创建一个属性,名称为propName
3. 将obj.propName的值设为value
可以看到,设值过程并不会考虑Prototype链,道理很明显,obj的内部[[Prototype]]是一个实例化的对象,它不仅仅向obj共享属性,还可能向其它对象共享属性,修改它可能影响其它对象。
用上面CF, Cfp的示例来说明,实例对象cf1具有本地属性q1, q2以及继承属性CFP1,如果执行cf1.CFP1="",那么cf1就具有本地属性CFP1了,测试结果如下:
复制代码 代码如下:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var cf1=new CF("aaa", "bbb");
var cf2=new CF(111, 222);
document.write(cf1.CFP1 + "<br />"); //result: CFP1 in Cfp
document.write(cf2.CFP1 + "<br />"); //result: CFP1 in Cfp
//it will result in a local property in cf1
cf1.CFP1="new value for cf1";
//changes on CF.prototype.CFP1 will affect cf2 but not cf1, because there's already a local property with
//the name CFP1 in cf1, but no such one in cf2
CF.prototype.CFP1="new value for Cfp";
document.write(cf1.CFP1 + "<br />"); //result: new value for cf1
document.write(cf2.CFP1 + "<br />"); //result: new value for Cfp
语义上的混乱?
还是使用上面CF, Cfp示例的场景。
根据Prototype的机制,我们可以说对象cf1, cf2等都继承了对象Cfp的属性和方法,所以应该说他们之间存在继承关系。属性的继承/共享是沿着隐式Prototype链作用的,所以继承关系也应当理解为沿着这个链。
我们再看instanceOf操作,只有cf1 instanceOf CF才成立,我们说cf1是CF的实例对象,CF充当了类的角色,而不会说cf1是Cfp的实例对象,这样我们应当说cf1继承自CF? 但CF充当的只是一个第三方工厂的角色,它跟cf1之间并没有属性继承这个关系。
把CF, Cfp看作一个整体来理解也同样牵强。
Prototype就是Prototype,没有必要强把JavaScript与面向对象概念结合起来, JavaScript只具备有限的面向对象能力,从另外的角度我们可以把它看成函数语言、动态语言,所以它是吸收了多种语言特性的精简版。
对象模型
Where are we?
1. 了解了JavaScript的数据类型,清楚了象Number这样的系统内置对象具有多重身份: a)它们本身是一个函数对象,只是由引擎内部实现而已,b)它们代表一种数据类型,我们可以用它们定义、操作相应类型的数据,c)在它们背后隐藏了引擎的内部实现机制,例如内部的数据结构、各种被包装成了JavaScript对象的构造器等。
2. 了解了Prototype机制,知道对象是如何通过它们继承属性和方法,知道了在创建对象过程中JS引擎内部是如何设置Prototype关系的。
接下来对用户自定义函数对象本身的创建过程进行了解之后,我们就可以对JavaScript的对象模型来一个整体性的overview了。
函数对象创建过程
JavaScript代码中定义函数,或者调用Function创建函数时,最终都会以类似这样的形式调用Function函数:var newFun=Function(funArgs, funBody); 。创建函数对象的主要步骤如下:
1. 创建一个build-in object对象fn
2. 将fn的内部[[Prototype]]设为Function.prototype
3. 设置内部的[[Call]]属性,它是内部实现的一个方法,处理逻辑参考对象创建过程的步骤3
4. 设置内部的[[Construct]]属性,它是内部实现的一个方法,处理逻辑参考对象创建过程的步骤1,2,3,4
5. 设置fn.length为funArgs.length,如果函数没有参数,则将fn.length设置为0
6. 使用new Object()同样的逻辑创建一个Object对象fnProto
7. 将fnProto.constructor设为fn
8. 将fn.prototype设为fnProto
9. 返回fn
步骤1跟步骤6的区别为,步骤1只是创建内部用来实现Object对象的数据结构(build-in object structure),并完成内部必要的初始化工作,但它的[[Prototype]]、[[Call]]、[[Construct]]等属性应当为null或者内部初始化值,即我们可以理解为不指向任何对象(对[[Prototype]]这样的属性而言),或者不包含任何处理(对[[Call]]、[[Construct]]这样的方法而言)。步骤6则将按照前面描述的对象创建过程创建一个新的对象,它的[[Prototype]]等被设置了。
从上面的处理步骤可以了解,任何时候我们定义一个函数,它的prototype是一个Object实例,这样默认情况下我们创建自定义函数的实例对象时,它们的Prototype链将指向Object.prototype。
另外,Function一个特殊的地方,是它的[[Call]]和[[Construct]]处理逻辑一样。
JavaScript对象模型
红色虚线表示隐式Prototype链。
这张对象模型图中包含了太多东西,不少地方需要仔细体会,可以写些测试代码进行验证。彻底理解了这张图,对JavaScript语言的了解也就差不多了。下面是一些补充说明:
1. 图中有好几个地方提到build-in Function constructor,这是同一个对象,可以测试验证:
复制代码 代码如下: