首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > Web前端 >

原形、作用域、闭包的完整解释(一)

2012-10-29 
原型、作用域、闭包的完整解释(一)?最后,创建MyObject2的实例,然后将这个对象的引用赋值给变量objRef??? 给

原型、作用域、闭包的完整解释(一)

?

最后,创建MyObject2的实例,然后将这个对象的引用赋值给变量objRef

??? 给构造函数传递一个字符串作为第一个参数

?

??? 被变量objectRef引用的MyObject2实例拥有一个原型链。链中的第一个对象是MyObject1的实例,他被创建并赋值给MyObject2构造函数的prorotype属性。MyObject1的实例有一个原型,此对象由(引擎)实现分配给函数MyObject1的prototype属性。这个对象(译者注:指分配给MyObject1的prototype属性的对象)有一个原型,就是默认Object的原型,对应Object.prototype所引用的对象。Object.prototype是一个值为null的原型,所以原型链到这一点结束。

??? 当一个属性访问器试图从变量objectRef所引用的对象上读取一个命名属性时,整个原型链都会加入处理过程。在简单的情况下:

??? var val = objectRef.testString;

?

??? objectRef所引用的MyObject2的实例有一个名为”testString”的属性,所以它就是这个属性的值,设置为”String_Value”,它被赋值给变量val。然而:

??? var val = objectRef.testNumber;

?

??? 不能从MyObject2实例自身读取命名属性,因为它没有这个属性,但是变量val的值被设置为8而不是undefined,因为在对象自身上查找对应的属性失败之后,解释器会检查对象的原型。它的原型是MyObject1的实例,它被创建时有一个属性名为”testNumber”,并且把值8赋给了这个属性,所以属性访问器算出的值为8。MyObject1和MyObject2都没有定义toString方法,但是如果一个属性访问器试图从objectRef上读取toString属性的值:

??? var val = objectRef.toString;

?

??? 变量val被赋值为一个函数的引用。这个函数是Object.ptorotype的toString属性,它被返回是因为检查了objectRef的原型,当发现objectRef没有”toString”属性,而是引用一个对象,所以当发现原型中也缺少此属性时,则继续查找原型的原型。它的原型是Object.prorotype,确实有一个toString方法,所以val就被赋值为一个引用,指向返回的函数对象。

??? 最后:

??? var val = objectRef.madeUpProperty;

?

??? 返回undefined,因为处理过程检查了整个原型链,发现没有哪个对象有一个名为"madeUpPeoperty"的属性,它最终获得了Object.prototype的值,这是一个null值,然后处理过程返回一个undefined。

??? 对命名属性的读取操作会返回第一个找到的值,这个值可以在对象上或者在它的原型链上。给对象的命名属性赋值时,如果对象上没有对应的属性存在,将会在对象自身创建一个属性。

??? 这意味着,如果一个值被分配为objectRef.testNumber=3,一个”testNumber”属性将会在MyObject2的实例自身上被创建,随后所有读取这个属性的操作将会获得在对象上设置的值。不再需要检查原型链来解析属性访问器,但是MyObject1的实例,”testNumber”属性被赋值为8不会变。objectRef对象上分配的对应属性遮盖了其原型链上的对应属性。

??? 注意:ECMAScript为内部Object类型定义了一个内部[[prototype]]属性。这个属性不能直接通过脚本访问,但是它是一个引用对象原型链的对象链,同时内部[[prototype]]属性在属性访问解析中被使用。存在一个公共的prototype属性与内部[[prototype]]属性相关联,允许对它进行赋值、定义和操作。这两者之间关系的细节在ECMA 262 (第三版)中有描述,这已经超越了本文的讨论范围。

?

标识符解析,执行环境和作用域链执行环境

??? 执行环境是ECMSScript规范使用的一个抽象概念(ECMA 262 第三版),用来定义ECMAScript实现所必须的行为。规范中没有提到关于执行环境应该如何实现的任何事情,但是执行环境有关联的属性,它引用了规范所定义的结构,所以它们可以被设想(甚至实现)为拥有属性的对象,虽然不是公共属性。

??? 所有JavaScript代码都是在一个执行环境中被执行的。全局代码(内嵌代码,一般作为js文件或者HTML页面加载)在全局执行环境中执行,每次函数调用(可以是构造器)都有一个分配的执行环境。用eval函数执行的代码也有一个独特的执行环境,但是因为eval从来不会被JavaScript程序员经常使用,所以这里不讨论它。执行环境的特定的细节可以在ECMA262的10.2(第三版)节找到。

??? 当一个JavaScript函数被调用的时候,它进入一个执行环境,如果其它函数被调用(或者在相同的函数上递归),一个新的执行环境被创建,在函数调用过程中进入此环境。当这个被调用的函数返回之后会返回先前的执行环境。这样,运行中的javascript代码就形成了一个执行环境栈

??? 当一个执行环境被创建时,很多事情按照规定的顺序发生。首先,在一个函数的执行环境中,一个“活动”对象被创建。活动对象是另一个规范机制。它可以被看做一个对象,因为它最终拥有可访问的命名属性,但是它不是一个普通对象,因为它没有原型(prototype)(至少不是一个已定义的prototype),并且它不能直接被javascript代码引用。

??? 为调用函数创建执行环境的下一步是创建一个arguments对象,这是一个类似数组的对象,拥有以整数值为下标的成员,它和调用函数时传递的参数按次序对应。arguments对象还有length和callee两个属性(和这里的讨论无关,参见细节描述)。活动对象会被创建一个名为”arguments”的属性,它会被赋值为一个引用,指向arguments对象。

??? 然后为执行环境分配一个作用域。作用域由一组对象列表(或链)组成。每个函数对象都有一个内部[[scope]]属性(稍后我们将涉及更多细节),它也由一组对象列表(或链)组成。调用函数时分配给执行环境的作用域(scope)由一个列表组成,这个列表就是被对应函数对象的[[scope]]属性所引用的列表,并且把活动对象添加到链(或者列表顶端)的前端而组成。

??? 然后,使用一个ECMA262所指的”可变”(Variable)对象执行“变量初始化”的过程。然而,活动对象被用作可变对象(注意,很重要:它们是同一个对象)。可变对象的命名参数是为函数的每一个形参创建的,并且,如果调用函数的参数与这些参数对应,这些参数的值会被赋到属性上(否则,赋值为undefined)。内部函数定义也被用来创建函数对象,它们也被设置到可变对象的属性上,属性名和定义函数时的函数名对应。变量初始化的最后一步是创建可变对象的命名属性,对应函数中声明的所有局部变量。

??? ?在可变对象上创建的,与申明的局部变量对应的属性,在变量初始化时被初始为undefined,局部变量的初始化实际不会发生,直到执行函数体中代码对相应的表达式时才进行计算。

??? 事实上,拥有arguments属性的“活动对象”,和拥有对应到函数局部变量的命名属性的“可变对象”,是同一个对象,这就允许把arguments标识符当作函数的局部变量看待。

??? 最后,为使用this关键字分配一个值。如果分配的值指向一个对象,那么使用this关键为前缀的属性访问器将会引用这个对象的属性。如果分配(内部)的值为null,那么this关键字将会指向全局对象。

??? 全局执行环境做了一些细微不同的处理,因为它没有参数,所以它没有必要定义活动对象去引用它们。全局执行环境确实需要一个作用域,并且它的作用域链仅仅由一个对象构成,就是全局对象。全局执行环境也需要经历变量初始化,它的内部函数是所声明的普通顶级函数,它们组成了javascript代码的绝大部分。全局对象被用作可变对象,这就是为什么声明的全局函数成为了全局对象的属性。全局范围内声明的变量也一样。

??? 全局执行环境也使用一个到全局对象的引用作为this对象。

作用域链和[[scope]]

??? 调用函数时执行环境中的作用域链,是通过把执行环境中的活动对象/可变对象添加到作用域链顶部而构成,作用域链由函数对象的[[scope]]属性持有,所以,理解[[scope]]属性是如何定义的非常重要。

??? 在ECMAScript中,函数是对象,它们在变量初始化时被创建:函数申明、执行函数表达式或者通过调用Function构造器。

??? 使用Function构造器创建的函数对象,总是把[[scope]]属性指向一个作用域链,其中仅仅包含全局对象。

??? 通过函数声明或者函数表达式创建的函数对象,拥有一个执行环境中的作用域链,被赋值给它们内部的[[scope]]属性,它们是在这个执行环境中被创建的。

??? 全局函数声明最简单的情况如:

?

??? function exampleFunction(formalParameter){

??? ??? ...?? // function body code

??? }

?

??? 在为全局执行环境进行变量初始化时,对应的函数对象被创建。全局执行环境有一个作用域链,它仅仅由全局对象组成。这样,这个被创建的函数对象被全局对象以”exampleFunction”为名称引用,并且被分配了一个内部[[scope]]属性,它引用一个作用域链,其中只包含全局对象。

??? 当一个函数表达式在全局环境中被执行时,分配一个类似的作用域链:

??? var exampleFuncRef = function(){

??? ??? ...?? // function body code

??? }

?

??? 在这种情况下,在为全局执行环境进行变量初始化的过程中,全局对象的一个命名属性被创建,并且它的一个引用被赋值给全局对象的一个命名属性,但是函数对象没有被创建,直到对赋值表达式求值为止。但是在全局环境中创建函数对象的操作还是发生了,所以创建的函数对象的[[scope]]属性仍然指向了只包含一个全局对象的作用域链。

??? 内部函数声明和以函数对象为结果的表达式在函数内部的执行环境中被创建,所以它们拥有更精细的作用域链。考虑以下代码,定义了一个有内部函数声明的函数,然后执行外部函数:

functionexampleOuterFunction(formalParameter){

??? function exampleInnerFuncitonDec(){

??????? ... // inner function body

??? }

??? ...?// the rest of the outer function body.

}

exampleOuterFunction(5 );

?

??? 对应着外部函数声明的函数对象在全局执行环境的变量初始化过程中被创建,所以它的[[scope]]属性包含只有一个元素的作用域链,其中只有一个全局对象。

??? 当外部代码调用exampleOuterFunction时,会为这个函数调用创建一个新的执行环境,同时还有和它一起的活动对象/可变对象。新执行环境中的作用域构成变成了:新的活动对象加上外部函数对象[[scope]]属性(仅包含全剧对象)所引用的作用域链。为新执行环境进行变量初始化导致创建了一个函数对象,它对应内部函数定义,并且这个函数对象的[[scope]]属性被分配为它被创建时执行环境中的作用域值。作用域链包含了活动对象,紧接着是全局对象。

1 楼 mr.lili 2011-12-29   MM                     

热点排行