js
闭包
JavaScript
/* 创建全局变量 - y - 它引用一个对象:- */
/* – 或- */
objectRef["testNumber"] = 5;
/* – or:- */
objectRef["testNumber"] = 8;
objectRef.testNumber = 8;
var val = objectRef.testNumber;Object 构造函数的默认原型就有一个 null 原型,因此:Object.prototype 的对象,而该原型自身则拥有一个值为 null 的原型。也就是说, objectRef 的原型链中只包含一个对象-- Object.prototype。但对于下面的代码而言:
function MyObject1(formalParameter){
/* 给创建的对象添加一个名为 – testNumber – 的属性
并将传递给构造函数的第一个参数指定为该属性的值:*/
this.testNumber = formalParameter;
}
/* 创建 – MyObject2 – 类型对象的函数*/
function MyObject2(formalParameter){
/* 给创建的对象添加一个名为 – testString – 的属性
并将传递给构造函数的第一个参数指定为该属性的值:*/
this.testString = formalParameter;
}
MyObject2.prototype = new MyObject1( 8 );
var objectRef = new MyObject2( “String_Value” );objectRef 所引用的 MyObject2 的实例拥有一个原型链。该链中的第一个对象是在创建后被指定给 MyObject2 构造函数的 prototype 属性的 MyObject1 的一个实例。MyObject1 的实例也有一个原型,即与 Object.prototype 所引用的对象对应的默认的 Object 对象的原型。最后, Object.prototype 有一个值为 null 的原型,因此这条原型链到此结束。objectRef 所引用的对象的属性值时,整个原型链都会被搜索。在下面这种简单的情况下:objectRef 所引用的 MyObject2 的实例有一个名为“testString”的属性,因此被设置为“String_Value”的该属性的值被赋给了变量 val。但是:MyObject2 实例自身中读取到相应的命名属性值,因为该实例没有这个属性。然而,变量 val 的值仍然被设置为 8,而不是未定义--这是因为在该实例中查找相应的命名属性失败后,解释程序会继续检查其原型对象。而该实例的原型对象是 MyObject1 的实例,这个实例有一个名为“testNumber”的属性并且值为 8,所以这个属性访问器最后会取得值 8。而且,虽然 MyObject1 和 MyObject2 都没有定义 toString 方法,但是当属性访问器通过 objectRef 读取 toString 属性的值时:val 也会被赋予一个函数的引用。这个函数就是在 Object.prototype 的 toString 属性中所保存的函数。之所以会返回这个函数,是因为发生了搜索 objectRef 原型链的过程。当在作为对象的 objectRef 中发现没有“toString”属性存在时,会搜索其原型对象,而当原型对象中不存在该属性时,则会继续搜索原型的原型。而原型链中最终的原型是Object.prototype,这个对象确实有一个 toString 方法,因此该方法的引用被返回。undefined,因为在搜索原型链的过程中,直至 Object.prototype 的原型--null,都没有找到任何对象有名为“madeUpPeoperty”的属性,因此最终返回 undefined。objectRef.testNumber = 3 这样一条赋值语句,那么这个 MyObject2 的实例自身也会创建一个名为“testNumber”的属性,而之后任何读取该命名属性的尝试都将获得相同的新值。这时候,属性访问器不会再进一步搜索原型链,但 MyObject1 实例值为 8 的“testNumber”属性并没有被修改。给 objectRef 对象的赋值只是遮挡了其原型链中相应的属性。[[prototype]] 属性。这个属性不能通过脚本直接访问,但在属性访问器解析过程中,则需要用到这个内部 [[prototype]] 属性所引用的对象链--即原型链。可以通过一个公共的prototype 属性,来对与内部的 [[prototype]] 属性对应的原型对象进行赋值或定义。这两者之间的关系在 ECMA 262(3rd edition)中有详细描述,但超出了本文要讨论的范畴。HTML 页面加载的代码)是在我称之为“全局执行环境”的执行环境中执行的,而对函数的每次调用(
有可能是作为构造函数)同样有关联的执行环境。通过 eval 函数执行的代码也有截然不同的执行环境,但因为 JavaScript 程序员在正常情况下一般不会使用 eval,所以这里不作讨论。有关执行环境的详细说明请参阅 ECMA 262(3rd edition)第 10.2 节。arguments 对象,这是一个类似数组的对象,它以整数索引的数组成员一一对应地保存着调用函数时所传递的参数。这个对象也有 length 和 callee 属性(这两个属性与我们讨论的内容无关,详见规范)。然后,会为活动对象创建一个名为“arguments”的属性,该属性引用前面创建的 arguments对象。[[scope]] 属性(该属性我们稍后会详细介绍),这个属性也由对象列表(链)组成。指定给一个函数调用执行环境的作用域,由该函数对象的 [[scope]] 属性所引用的对象列表(链)组成,同时,活动对象被添加到该对象列表的顶部(链的前端)。undefined 值)。对于定义的内部函数,会以其声明时所用名称为可变对象创建同名属性,而相应的内部函数则被创建为函数对象并指定给该属性。变量实例化的最后一步是将在函数内部声明的所有局部变量创建为可变对象的命名属性。undefined 值。在执行函数体内的代码、并计算相应的赋值表达式之前不会对局部变量执行真正的实例化。arguments 属性的活动对象和拥有与函数局部变量对应的命名属性的可变对象是同一个对象。因此,可以将标识符 arguments 作为函数的局部变量来看待。this 关键字而赋值。如果所赋的值引用一个对象,那么前缀以 this 关键字的属性访问器就是引用该对象的属性。如果所赋(内部)值是 null,那么 this 关键字则引用全局对象。this 对象来引用全局对象。[[scope]] 属性中的作用域链前端而构成的。所以,理解函数对象内部的 [[scope]] 属性的定义过程至关重要。Function 构造函数时创建。Function 构造函数创建的函数对象,其内部的 [[scope]] 属性引用的作用域链中始终只包含全局对象。[[scope]] 属性引用的则是创建它们的执行环境的作用域链。
… // 函数体内的代码
}[[scope]] 属性,赋予了只包含全局对象的作用域链。
… // 函数体代码
}[[scope]] 属性指定的作用域链中仍然只包含全局对象。内部的函数声明或表达式会导致在包含它们的外部函数的执行环境中创建相应的函数对象,因此这些函数对象的作用域链会稍微复杂一些。在下面的代码中,先定义了一个带有内部函数声明的外部函数,然后调用外部函数:
var y = {x:5}; // 带有一个属性 - x - 的对象直接量
function exampleFuncWith(){
var z;
/* 将全局对象 - y - 引用的对象添加到作用域链的前端:- */
with(y){
/* 对函数表达式求值,以创建函数对象并将该函数对象的引用指定给局部变量 - z - :- */
z = function(){
... // 内部函数表达式中的代码;
}
}
...
}
/* 执行 - exampleFuncWith - 函数:- */
exampleFuncWith 函数创建的执行环境中包含一个由其活动对象后跟全局对象构成的作用域链。而在执行 with 语句时,又会把全局变量 y 引用的对象添加到这个作用域链的前端。在对其中的函数表达式求值的过程中,所创建函数对象的 [[scope]] 属性与创建它的执行环境的作用域保持一致--即,该属性会引用一个由对象 y 后跟调用外部函数时所创建执行环境的活动对象,后跟全局对象的作用域链。with 语句相关的语句块执行结束时,执行环境的作用域得以恢复(y 会被移除),但是已经创建的函数对象(z。译者注)的 [[scope]] 属性所引用的作用域链中位于最前面的仍然是对象 y。Array.prototype.join 方法(以空字符串作为其参数)输出结果。这个数组将作为输出的缓冲器,但是将数组作为函数的局部变量又会导致在每次调用函数时都重新创建一个新数组,这在每次调用函数时只重新指定数组中的可变内容的情况下并不是必要的。
/* 声明一个全局变量 - getImgInPositionedDivHtml -
并将一次调用一个外部函数表达式返回的内部函数赋给它。
这个内部函数会返回一个用于表示绝对定位的 DIV 元素
包围着一个 IMG 元素 的 HTML 字符串,这样一来,
所有可变的属性值都由调用该函数时的参数提供:
*/
var getImgInPositionedDivHtml = (function(){
/* 外部函数表达式的局部变量 - buffAr - 保存着缓冲数组。
这个数组只会被创建一次,生成的数组实例对内部函数而言永远是可用的
因此,可供每次调用这个内部函数时使用。
其中的空字符串用作数据占位符,相应的数据
将由内部函数插入到这个数组中:
*/
var buffAr = [
'<div id="',
'', //index 1, DIV ID 属性
'" style="position:absolute;top:',
'', //index 3, DIV 顶部位置
'px;left:',
'', //index 5, DIV 左端位置
'px;width:',
'', //index 7, DIV 宽度
'px;height:',
'', //index 9, DIV 高度
'px;overflow:hidden;\"><img src=\"',
'', //index 11, IMG URL
'\" width=\"',
'', //index 13, IMG 宽度
'\" height=\"',
'', //index 15, IMG 高度
'\" alt=\"',
'', //index 17, IMG alt 文本内容
'\"></div>'
];
/* 返回作为对函数表达式求值后结果的内部函数对象。
这个内部函数就是每次调用执行的函数
- getImgInPositionedDivHtml( ... ) -
*/
return (function(url, id, width, height, top, left, altText){
/* 将不同的参数插入到缓冲数组相应的位置:*/
buffAr[1] = id;
buffAr[3] = top;
buffAr[5] = left;
buffAr[13] = (buffAr[7] = width);
buffAr[15] = (buffAr[9] = height);
buffAr[11] = url;
buffAr[17] = altText;
/* 返回通过使用空字符串(相当于将数组元素连接起来)
连接数组每个元素后形成的字符串:
*/
return buffAr.join('');
}); //:内部函数表达式结束。
})();
/*^^- :单行外部函数表达式。*/
/* 定义一个全局变量,通过下面的函数将它的值 /* 定义一个全局变量,通过下面的函数将它的值 function ExampleConst(param){ function ExampleConst(param){
作为查询字符串的一部分添加到链接的 - href - 中:
*/
var quantaty = 5;
/* 当给这个函数传递一个链接(作为函数中的参数 - linkRef -)时,
会将一个 onclick 事件处理器指定给该链接,该事件处理器
将全局变量 - quantaty - 的值作为字符串添加到链接的 - href -
属性中,然后返回 true 使该链接在单击后定位到由 - href -
属性包含的查询字符串指定的资源:
*/
function addGlobalQueryOnClick(linkRef){
/* 如果可以将参数 - linkRef - 通过类型转换为 ture
(说明它引用了一个对象):
*/
if(linkRef){
/* 对一个函数表达式求值,并将对该函数对象的引用
指定给这个链接元素的 onclick 事件处理器:
*/
linkRef.onclick = function(){
/* 这个内部函数表达式将查询字符串
添加到附加事件处理器的元素的 - href - 属性中:
*/
this.href += ('?quantaty='+escape(quantaty));
return true;
};
}
}addGlobalQueryOnClick 函数,都会创建一个新的内部函数(通过赋值构成了闭包)。从效率的角度上看,如果只是调用一两次 addGlobalQueryOnClick 函数并没有什么大的妨碍,但如果频繁使用该函数,就会导致创建许多截然不同的函数对象(每对内部函数表达式求一次值,就会产生一个新的函数对象)。
作为查询字符串的一部分添加到链接的 - href - 中:
*/
var quantaty = 5;
/* 当把一个链接(作为函数中的参数 - linkRef -)传递给这个函数时,
会给这个链接添加一个 onclick 事件处理器,该事件处理器会
将全局变量 - quantaty - 的值作为查询字符串的一部分添加到
链接的 - href - 中,然后返回 true,以便单击链接时定位到由
作为 - href - 属性值的查询字符串所指定的资源:
*/
function addGlobalQueryOnClick(linkRef){
/* 如果 - linkRef - 参数能够通过类型转换为 true
(说明它引用了一个对象):
*/
if(linkRef){
/* 将一个对全局函数的引用指定给这个链接
的事件处理属性,使函数成为链接元素的事件处理器:
*/
linkRef.onclick = forAddQueryOnClick;
}
}
/* 声明一个全局函数,作为链接元素的事件处理器,
这个函数将一个全局变量的值作为要添加事件处理器的
链接元素的 - href - 值的一部分:
*/
function forAddQueryOnClick(){
this.href += ('?quantaty='+escape(quantaty));
return true;
}
/* 通过对函数表达式求值创建对象的方法,
并将求值所得的函数对象的引用赋给要创建对象的属性:
*/
this.method1 = function(){
... // 方法体。
};
this.method2 = function(){
... // 方法体。
};
this.method3 = function(){
... // 方法体。
};
/* 把构造函数的参数赋给对象的一个属性:*/
this.publicProp = param;
}new ExampleConst(n) 使用这个构造函数创建一个对象时,都会创建一组新的、作为对象方法的函数对象。因此,创建的对象实例越多,相应的函数对象也就越多。prototype 的相应属性显然更有效率。这样一来,它们就能被构造函数创建的所有对象共享了:
/* 将构造函数的参数赋给对象的一个属性:*/
this.publicProp = param;
}
/* 通过对函数表达式求值,并将结果函数对象的引用
指定给构造函数原型的相应属性来创建对象的方法:
*/
ExampleConst.prototype.method1 = function(){
... // 方法体。
};
ExampleConst.prototype.method2 = function(){
... // 方法体。
};
ExampleConst.prototype.method3 = function(){
... // 方法体。
};
