js模型

 

第三章 对象基础(对象模型)

3.1对象创建过程

JS中只有函数对象具备类的概念,因此要创建一个对象,必须使用函数对象。函数对象内部有[[Construct]]方法和[[Call]]方法,[[Construct]]用于构造对象,[[Call]]用于函数调用,只有使用new操作符时才触发[[Construct]]逻辑var obj=new Object();(对象创建表达式) 是使用内置的Object这个函数对象创建实例化对象obj。var obj={};(对象初始化表达式)var obj=[];(数组初始化表达式)这种代码将由JS引擎生成Objec、Array的实例,生成效率高,并不调用Object和Array的构造函数。function Fn(){}; var myObj=new Fn();(对象创建表达式)是使用用户定义的类型创建实例化对象。

new Fn(args)的创建过程如下(即函数对象的[[Construct]]方法处理逻辑,对象的创建过程)。另外函数对象本身的创建过程(指定义函数或者用Function创建一个函数对象等方式)虽然也使用了下面的处理逻辑,但有特殊的地方,后面再描述。

 

对象创建过程([[Construc]](args))

new Fn(args):使用new操作符时触发Fn 的[[Construct]]逻辑,创建对象的步骤如下:

 

  1. 创建一个build-in object对象obj(实例对象);

(并完成内部必要的初始化,它的[[Class]]、[[Extensible]]、[[Prototype]]等属性应当为null或者内部初始化值。)

  1. 设置obj的内部属性[[Class]]为”Object”,或者其它本地类型,例如:”Number”、”String”、

“Boolean”等。这是由构造函数的类型来定的。

3.  设置obj的内部属性[[Extensible]]为 true。

4.  如果Fn.prototype是object引用对象类型,则将obj的内部[[Prototype]]设置为Fn.prototype,否则obj的[[Prototype]]将为其初始化值(即Object.prototype)

5.  obj作为this,使用args参数调用Fn的内部[[Call]]方法。(this.[[Call]](args))

5.1 创建内部[[Call]]方法的当前执行环境(当前执行环境压入执行环境栈)

(函数体function定义的方法的[[Scope]]静态作用域复制到[[Call]]的作用域链,在作用域链的前端添加[[Call]]的活动对象;this引用obj)

5.2 调用Fn的函数体

3.3 销毁当前的执行环境(当前执行环境弹出执行环境栈)

3.4 返回Fn函数体的返回值,如果Fn的函数体没有返回值则返回undefined

6. 如果返回值是object引用对象类型,则返回这个值;否则(返回值是基本类型)则返回obj

 

 

注意步骤4中, prototype指Fn对象显示的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 to 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

 

// fn.prototype.constructor == fn;  //result: false

// obj.[[ prototype]] == fn.prototype;  //result: true

//因此,obj继承了fn.prototypeconstructor ,

// obj. constructor  à obj.[[ prototype]]

//                   à fn.prototype  à fn.prototype. constructor -> fn

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

 

// fn.prototype.constructor == Object;  //result: true

// obj.[[ prototype]] == fn.prototype;  //result: true

//因此,obj继承了fn.prototypeconstructor ,

// obj. constructor  à obj.[[ prototype]]

 à fn.prototype  à fn.prototype. constructor -> Object

 

document.write(obj instanceof Object); //result: false

关于创建过程返回值的验证:

//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(){
    //according to step 4 described above,
    //the new fn() operation will return the object { attr1: 111, attr2: 222 }, it's not an instance of 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

 

 

 

3.2 自定义函数对象创建过程(类/函数/构造函数)

 

函数标识符:

  1. 符合语法
  2. 有意义
  3. 简洁
  4. 符合JavaScript的命名规范

 

 

  1. 使用function关键字定义函数对象

1)函数声明语句

如果函数声明只用于调用,则采用camel命名规范

function Func (a){document.write(a + "<br />");}

 

如果函数声明用于new,则采用Pascal命名规范

function func(a){document.write(a + "<br />");}

 

2)函数声明表达式

匿名函数声明表达式

     var test = function func (a){document.write(a + "<br />");}

 

命名函数声明表达式

     var test = function (a){document.write(a + "<br />");}

 

  1. 使用Function类定义函数对象

    具有动态性,它允许在程序运行期间动态地创建函数。

    语法:var 函数名 = new Function("形式参数1","形式参数2",...,"形式参数n","函数体");

    语法:var 函数名 = Function("形式参数1","形式参数2",...,"形式参数n","函数体");

 Function类的一个特殊地方,是它的[[Call]]和[[Construct]]处理逻辑一样。

new Function生成的函数的[[Scope]]永远都只包含window对象。

 

 

自定义函数(/类/构造函数)对象创建过程

JavaScript代码中使用关键词function定义函数,或者调用Function创建函数对象,其步骤如下:

 

  1. 创建一个build-in object对象fn

(并完成内部必要的初始化,它的 [[Prototype]] [[Class]]、[[Extensible]]、 [[Call]]、[[Construct]]等内部隐式属性应当为null或者内部初始化值。)

  1. 设置fn的内部隐式属性[[Class]]为” Function”。

3.  设置fn的内部隐式属性[[Extensible]]为true。

4.  设置fn的内部隐式属性[[Prototype]]为Function.prototype

5.  设置fn的内部隐式属性[[Call]],它是内部实现的一个方法(包含JS代码),处理逻辑参考对象创建过程的步骤5

6.  设置fn的内部隐式属性[[Construct]],它是内部实现的一个方法,处理逻辑参考对象创建过程的步骤1,2,3,4,5,6。

7. 设置fn的内部隐式属性[[Scope]]为:

7.1   使用function关键字声明的函数声明语句创建的函数对象,其[[Scope]]静态作用域复制当前执行环境的作用域链。

function Func (a){document.write(a + "<br />");}

function func (a){document.write(a + "<br />");}

7.2  使用function关键字声明的匿名函数表达式创建的函数对象,其[[Scope]]静态作用域复制当前执行环境的作用域链。

var test = function (a){document.write(a + "<br />");}

7.3  使用function关键字声明的命名函数表达式创建的函数对象,其[[Scope]]静态作用域复制当前执行环境的作用域链;在[[Scope]]静态作用域的前端添加一个Object类的实例;在该实例上添加一个属性,名字为函数名func,值为返回的函数对象fn。

var test = function func (a){document.write(a + "<br />");}

7.4   new Function()、Function()函数定义的函数对象的[[Scope]]静态作用域复制全局执行环境的作用域链

var fn = new Function("形式参数1","形式参数2",...,"形式参数n","函数体");

    var fn = Function("形式参数1","形式参数2",...,"形式参数n","函数体");

8. 设置fn.length为函数形参数量,如果函数没有参数,则将fn.length设置为0

9. 在函数对象fn内部设置形参列表。

10. 使用new Object()同样的逻辑创建一个Object类的实例对象fnProto

11. 将fnProto.constructor设为fn

   原型的构造器属性引用构造函数本身,对编码者来说意义不大,它仅是语法规范的一种实现。

12. 将fn.prototype设为fnProto

13. 返回fn

 

 

步骤1跟步骤10的区别为,步骤1只是创建内部用来实现object复杂对象的数据结构(build-in object structure),并完成内部必要的初始化工作,但它的[[Prototype]]、[[Call]]、[[Construct]]等属性应当为null或者内部初始化值,即我们可以理解为不指向任何对象(对[[Prototype]]这样的属性而言),或者不包含任何处理(对[[Call]]、[[Construct]]这样的方法而言)。步骤10则将按照前面描述的对象创建过程创建Object类的一个新对象,它的[[Prototype]]等被设置Object.protorype。

从上面的处理步骤可以了解,任何时候我们定义一个函数,它的prototype是一个Object类的实例,这样默认情况下我们创建自定义函数的实例对象时,它们的Prototype链将指向Object.prototype。

另外,Function或者Array类的一个特殊地方,是它的[[Call]]和[[Construct]]处理逻辑一样。而通常fn的[[Construct]]调用[[Call]],参见对象创建过程5。

 

 

3.3Function构造函数/Function类型

(相当于java的class反射类或者C#的type反射类)

 

 

3.4JavaScript对象模型

   
红色虚线表示隐式Prototype链。
 这张对象模型图中包含了太多东西,不少地方需要仔细体会,可以写些测试代码进行验证。彻底理解了这张图,对JavaScript语言的了解也就差不多了。下面是一些补充说明:
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 equals to Number.constructor, String.constructor, Array.constructor, RegExp.constructor, etc.
function fn(){}
Function==fn.constructor //result: true

这说明了几个问题: Function指向系统内置的函数构造器(build-in Function constructor);Function具有自举性;系统中所有函数都是由Function构造。

2. 左下角的obj1, obj2...objn范指用类似这样的代码创建的对象: function fn1(){}; var obj1=new fn1();
    这些对象没有本地constructor方法属性,但它们将从Prototype链上得到一个继承的constructor方法属性,即fn.prototype.constructor,从函数对象的构造过程可以知道,它就是fn本身了。
    右下角的obj1, obj2...objn范指用类似这样的代码创建的对象: var obj1=new Object();或var obj1={};或var obj1=new Number(123);或obj1=/\w+/;等等。所以这些对象Prototype链的指向、从Prototype链继承而来的constructor的值(指它们的constructor是build-in Number constructor还是build-in Object constructor等)等依赖于具体的对象类型。另外注意的是,var obj=new Object(123);这样创建的对象,它的类型仍然是Number,即同样需要根据参数值的类型来确定。
    同样它们也没有本地constructor,而是从Prototype链上获得继承的constructor方法,即build-in *** constructor,具体是哪一个由数据类型确定。

3. 关于图中

Prototype链的补充说明:
Object.prototype是整个链的终结点,它的内部[[Prototype]]为null。
所有函数的Prototype链都指向Function.prototype。
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链只有唯一的一个终结点。

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

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

 

 

 

 

Javascript引擎的内置对象/类是由ECMAScript实现提供的、与宿主无关,这些对象在程序执行之前就已经存在了,具有特殊的数据结构或Native Code ;其数据结构或Native Code 的行为与我们自定义的对象/函数不同;只能通过内置类的API来操作内置类的实例。即内置类或浏览器的数据结构只能通过API来操作;我们自定义的对象内部没有这些特殊的数据结构。

 

posted @ 2019-11-06 06:24  风中的猫  阅读(334)  评论(0编辑  收藏  举报