javascript对象模型(转)

javascript对象模型

数据类型
基本数据类型

基本数据类型是JS语言最底层的实现。
简单数值类型: 有Undefined, Null, Boolean, Number和String。注意,描述中的英文单词在这里仅指数据类型的名称,并不特指JS的全局对象#d70000, Boolean, Number, String等,它们在概念上的区别是比较大的。
对象: 一个无序属性的集合,这些属性的值为简单数值类型、对象或者函数。同上,这里的对象并不特指全局对象Object。
函数: 函数是对象的一种,实现上内部属性[[Class]]值为"Function",除了对象的内部属性方法外,还有[[Construct]]、[[Call]]、[[Scope]]等内部属性。函数作为函数调用与构造器(使用new关键字创建实例对象)的处理机制不一样(Function对象除外),内部方法[[Construct]]用于实现作为构造器的逻辑,方法[[Call]]实现作为函数调用的逻辑。同上,这里的函数并不特指全局对象Function。
函数在JS这个Prototype语言中可以看作是面向对象语言的类,可以用它来构造对象实例。既然函数可以看作是类,所以每一个函数可以看作是一种扩展数据类型。

内置数据类型(内置对象)
Function: 函数类型的用户接口。
Object: 对象类型的用户接口。
Boolean, Number, String: 分别为这三种简单数值类型的对象包装器,对象包装在概念上有点类似C#中的Box/Unbox。
Date, Array, RegExp: 可以把它们看作是几种内置的扩展数据类型。

首先,Function, Object, Boolean, Number, String, Date, Array, RegExp等都是JavaScript语言的内置对象,它们都可以看作是函数的派生类型,例如Number instanceof Function为true,Number instanceof Object为true。在这个意义上,可以将它们跟用户定义的函数等同看待。
其次,它们各自可以代表一种数据类型,由JS引擎用native code或内置的JS代码实现,是暴露给开发者对这些内置数据类型进行操作的接口。在这个意义上,它们都是一种抽象的概念,后面隐藏了具体的实现机制。
在每一个提到Number, Function等单词的地方,应该迅速的在思维中将它们实例化为上面的两种情况之一。

数据类型实现模型描述
   
Build-in *** data structure: 指JS内部用于实现***类型的数据结构,这些结构我们基本上无法直接操作。
Build-in *** object: 指JS内置的Number, String, Boolean等这些对象,这是JS将内部实现的数据类型暴露给开发者使用的接口。
Build-in *** constructor: 指JS内置的一些构造器,用来构造相应类型的对象实例。它们被包装成函数对象暴露出来,例如我们可以使用下面的方法访问到这些函数对象:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
//
access the build-in number constructor
var number =
new Number(123);
var numConstructor1 = number.constructor; //or
var numConstructor2 =
new Object(123).constructor;
numConstructor1
== numConstructor2 //result: true
//
access the build-in object constructor
var objConstructor1 = {}.constructor; //or
var objConstructor2 =
new Object().constructor;
objConstructor1
==objConstructor2 //result: true


具体实现上,上图中横向之间可能也存在关联,例如对于build-in data structure和constructor,Function、 Date、 Array、 RegExp等都可以继承Object的结构而实现,但这是具体实现相关的事情了。

关于简单数值类型的对象化
这是一个细微的地方,下面描述对于Boolean, String和Number这三种简单数值类型都适用,以Number为例说明。
JS规范要求: 使用var num1=123;这样的代码,直接返回基本数据类型,就是说返回的对象不是派生自Number和Object类型,用num1 instanceof Object测试为false;使用new关键字创建则返回Number类型,例如var num2=new Number(123); num2 instanceof Number为true。
将Number当作函数调用,返回结果会转换成简单数值类型。下面是测试代码:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var num1 =
new Number(123); //num1 derived from Number & Object
num1 instanceof Number //result: true
num1 instanceof Object //result: true
num1 = Number(num1); //convert the num1 from Number to primitive type
num1 instanceof Number //result: false
num1 instanceof Object //result: false
var num2 =
123; //num2 is a primitive type
num2 instanceof Number //result: false
num2 instanceof Object //result: false

虽然我们得到了一个简单数值类型,但它看起来仍然是一个JS Object对象,具有Object以及相应类型的所有属性和方法,使用上基本没有差别,唯一不同之处是instanceof的测试结果。

Prototype继承
Prototype

每个对象都有一个[[Prototype]]的内部属性,它的值为null或者另外一个对象。函数对象都有一个显示的prototype属性,它并不是内部[[Prototype]]属性。不同的JS引擎实现者可以将内部[[Prototype]]属性命名为任何名字,并且设置它的可见性,只在JS引擎内部使用。虽然无法在JS代码中访问到内部[[Prototype]](FireFox中可以,名字为__proto__因为Mozilla将它公开了),但可以使用对象的isPrototypeOf()方法进行测试,注意这个方法会在整个Prototype链上进行判断。
使用obj.propName访问一个对象的属性时,按照下面的步骤进行处理(假设obj的内部[[Prototype]]属性名为__proto__):
1. 如果obj存在propName属性,返回属性的值,否则
2. 如果obj.__proto__为null,返回undefined,否则
3. 返回obj.__proto__.propName
调用对象的方法跟访问属性搜索过程一样,因为方法的函数对象就是对象的一个属性值。

例如下图所示,object1将具备属性prop1, prop2, prop3以及方法fn1, fn2, fn3。图中虚线箭头表示prototype链。
   
这就是基于Prototype的继承和共享。其中object1的方法fn2来自object2,概念上即object2重载了object3的方法fn2。
JavaScript对象应当都通过prototype链关联起来,最顶层是Object,即对象都派生自Object类型。

类似C++等面向对象语言用类(被抽象了的类型)来承载方法,用对象(实例化对象)承载属性,Prototype语言只用实例化的对象来承载方法和属性。本质区别是前者基于内存结构的描述来实现继承,后者基于具体的内存块实现。

对象创建过程
JS中只有函数对象具备类的概念,因此要创建一个对象,必须使用函数对象。函数对象内部有[[Construct]]和[[Call]]两种处理逻辑,[[Construct]]逻辑用于构造对象,[[Call]]用于函数调用,只有使用new操作符时才触发[[Construct]]逻辑。
var obj=new Object(); 是使用内置的Object这个函数对象创建实例化对象obj。var obj={};和var obj=[];这种代码将由JS引擎触发Object的构造过程。function fn(){}; var myObj=new fn();是使用用户定义的类型创建实例化对象。

new F(args)的创建过程如下:
1. 创建一个Object对象obj并初始化
2. 如果F.prototype是Object类型,则将obj的内部[[Prototype]]设置为F.prototype,否则obj的[[Prototype]]将为其初始化值(即Object.prototype)
3. 将obj作为this,使用args参数调用F的内部[[Call]]方法
    3.1 内部[[Call]]方法创建当前执行上下文
    3.2 调用F的函数体
    3.3 销毁当前的执行上下文
    3.4 返回F函数体的返回值,如果F的函数体没有返回值则返回undefined
4. 如果[[Call]]的返回值是Object类型,则返回这个值,否则返回obj
注意步骤2中, prototype指对象显示的prototype属性,而[[Prototype]]则代表对象内部Prototype属性(隐式的)。
构成对象Prototype链的是内部隐式的[[Prototype]],而并非对象显示的prototype属性。显示的prototype只有在函数对象上才有意义,从上面的创建过程可以看到,函数的prototype被赋给派生对象隐式[[Prototype]]属性,这样根据Prototype规则,派生对象和函数的prototype对象之间才存在继承关系。

用代码来做一些验证:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(){}
//the value of implicit [[Prototype]] property of those objects derived from fn will be assigned as fn.prototype
fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new fn();
document.write(obj.attr1
+
"<br />"); //result: aaa
document.write(obj.attr2 +
"<br />"); //result: bbb
document.write(obj instanceof fn); //result: true
document.write("<br />");
//I change the prototype of fn here, so by the algorithm of Prototype the obj is no longer the instance of fn,
//
but this won't affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties
fn.prototype={};
document.write(obj.attr1
+
"<br />"); //result: aaa
document.write(obj.attr2 +
"<br />"); //result: bbb
document.write(obj instanceof fn); //result: false

关于创建过程返回值的验证:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(){
   
return { attr1: 111, attr2: 222 };
}
fn.prototype
={ attr1:"aaa", attr2:"bbb"};
var obj=new fn();
document.write(obj.attr1
+
"<br />"); //result: 111
document.write(obj.attr2 +
"<br />"); //result: 222
document.write(obj instanceof fn); //result: false


对于下面这幅图,经过上面的理解应当可以写出它的实现代码了
   
CF是函数,Cfp是CF的prototype对象,cf1, cf2, cf3, cf4, cf5都是CF的实例对象。虚线箭头表示隐式Prototype关系,实线箭头表示显示prototype关系。
//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
cf1.CFP1="new value for cf1";
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只具备有限的面向对象能力,从另外的角度我们可以把它看成函数语言、动态语言,所以它是吸收了多种语言特性的精简版。

对象模型
上面讲的是为了理解下面这张图,彻底理解了这张图,JavaScript也掌握的差不多了。
   
红色虚线表示隐式Prototype链,蓝色实线表示显示prototype关系。这张对象模型图中包含了太多东西,下面是一些说明:
1. 图中有好几个地方提到build-in Function constructor,这是同一个对象,可以测试验证:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
Function==Function.constructor //result: true
Function==Function.prototype.constructor //result: true
Function==Object.constructor //result: true
//
Function also equels to Number.constructor, String.constructor, Array.constructor, RegExp.constructor, etc.
function fn(){}
Function
==fn.constructor //result: true

这说明系统中所有函数都是由Function,也就是内置的Function constructor构造出来。

2. 关于隐式Prototype链:
Object.prototype是整个链的终结点,它的内部[[Prototype]]为null。
所有函数的Prototype链都指向Function.prototype,包括用户自定义的函数以及所有的内置构造器(build-in *** constructors)。这是因为所有函数都是由Function创建(设计者的意图是这样,至于实现者在内部实现上是否遵循这一点规范中是没有要求的)。
Function的Prototype链指向Function.prototype,这是规范要求的,因为设计者将Function设计为具有自举性。Function的Prototype链这样设计之后,Function.constructor==Function, Function instanceOf Function都为true。另外Function已经是最顶层的构造器,但Function本身也是一个函数对象,这样自举在语义上合情合理。
Function.prototype的Prototype链指向Object.prototype,这也是规范强制要求的。首先Function.prototype是Function的一个实例对象(typeof Function.prototype可以知道它是一个Function,instanceOf无法通过测试,因为Prototype链在内部被额外设置了),所以按照Prototype的规则,Function.prototype的内部[[Prototype]]值应当为Function.prototype这个对象,即它的Prototype链指向自己本身。这样一方面在Prototype链上造成一个死循环,另一方面它本身成为了一个终结点,结果就是所有函数对象将不是派生自Object了。加上这个强制要求之后,Prototype链只有唯一的一个终结点。

3. 因为Function.prototype是一个函数对象,所以它应当具有显示的prototype属性,即Function.prototype.prototype,但只有FireFox中可以访问到,IE、Opera、Safari都无法访问。所以图中用了个表示不存在的符号。

4. 用户自定义函数(user defined functions)默认情况下prototype属性值是Object.prototype,所以图中就这样表示了,但并不代表总是这样,当用户设置了自定义函数的prototype属性之后,情况就不同了。

5. 关于使用内置构造器创建的对象(objects that created by build-in constructors)。在第一幅图Data Structure Implementation Model中列出了内置的构造器,在这幅图中限于篇幅原因不能都画出来,所以用了简单的方式表示,但这样表达就不准确了。这些object的Prototype链的指向依赖于具体的数据类型,例如字符串类型的对象Prototype链指向String.prototype,而String.prototype则指向Object.prototype。另外需要注意到,用new Object()构造的对象,不一定直接就是Object类型的,它同样依赖于具体的参数类型。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值