`
caolixiang
  • 浏览: 8016 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

(译)理解JavaScript闭包

    博客分类:
  • Js
阅读更多

前言:

理解JavaScript闭包——Javascript Closures是一篇经典文章。网上(包括iteye)有翻译的中文版本,但是有一个部分并未翻译。在学习的过程中,我决定翻译下来,让这篇经典文章有一个完整的中文版。基于自己是第一次翻译,肯定存在一些错误,一些部分采用了意译。翻译之后,对译文进行了三遍润色和修改,希望大家提出意见,继续改进这篇译文。

最后,希望能给大家带来写帮助。

 

正文

标识符解析
标识符的解析依赖于作用链。ECMA262倾向于把this划归为关键词而不是标识符,解析总是依赖执行环境中使用的this的值而不是依赖对作用链,因此标识符的解析不是那么的合理。(译者注:含有this的情况)
Example 1:

 

 

Js代码  收藏代码
  1. var example = "CaoLixiang";  
  2. var outer = function() {  
  3.     var example = "HuJin";  
  4.     return function() {  
  5.         var example = "Love";  
  6.         alert(this.example);  
  7.     }  
  8. }  
  9. var test = outer();  
  10. test(); // CaoLixiang  

 

对“example”这个标识符的解析,依赖于this的值,指向全局对象,而不是作用链。调用test()时的作用链为:内部匿名函数的活动对象->outer的活动对象->全局对象,如果依赖作用链,将返回“Love”。
(译者注结束)
标识符解析从作用链的第一个对象开始。需找与标识符相对应的属性。因为作用链是一个对象链,如果在第一个对象对应的作用链中无法找到对应的属性,会继续在作用链中的第二个对象中寻找。
以此类推,在原型链的一个个对象中寻找是否存在与标识符对应的属性,直到作用链被穷尽。
(译者注)
Example 2:


Js代码  收藏代码
  1. var age = 200;  
  2. var sec = function() {  
  3.     var age = 100;  
  4.     return function() {  
  5.         var age = 22;  
  6.         alert(age);  
  7.     };  
  8. };  
  9. var test = sec();  
  10. test(); // 22  
  11.    
  12. var age = 200;  
  13. var sec = function() {  
  14.     var age = 100;  
  15.     return function() {  
  16.         alert(age);  
  17.     };  
  18. };  
  19. var test = sec();  
  20. test(); // 100  
  21.   
  22. var age = 200;  
  23. var sec = function() {  
  24.     age = 100;  
  25.     return function() {  
  26.         alert(age);  
  27.     };  
  28. };  
  29. var test = sec();  
  30. test(); // 200  
 
(译者注结束)
标识符的操作与标识符访问方式一样。作用链中有对应的属性的对象会成为标识符访问的对象,标识符将作为那个对象的属性名。注意:全局对象总是在作用链的最后端。
在执行环境中进行函数调用时,会将活动对象置于作用链前端,高效地进行一些检测操作:函数体中是否有对应的属性;内部函数是否声明了命名属性或者本地变量,所有这些将会被解析为活动对象中的命名属性。

闭包

垃圾自动回收机制
ECMA262有一个垃圾回收机制,语言标准中并未规定其实现的细节。因此有多种不同方式的实现方式,但其中一些实现的优先级很低。
通常认为当一个对象不被引用(正在执行的代码无法再引用),它就变为可被回收的状态,并且在未来某一个时间点上被销毁,它消耗的资源会被释放以供系统复用。
垃圾回收一般出现在执行环境中,作用链中,活动对象以及执行环境所创建的其他对象,当然也包括函数对象,当无法被访问时,就会置为可被回收的状态。

创建闭包
执行环境中一个函数被调用,在调用中,将一个返回它的内部函数对象的引用,将这个引用赋给另外一个函数的属性,就会形成闭包。也可以直接将这个函数对象的引用指定给,比方说,全局变量,或者一个全局课访问的对象的属性,或者按引用传递作为外部函数调用的一个参数。

Java代码  收藏代码
  1. function exampleClosureForm(arg1, arg2){  
  2.     var localVar = 8;  
  3.     function exampleReturned(innerArg){  
  4.         return ((arg1 + arg2)/(innerArg + localVar));  
  5.     }  
  6.     /* 返回对内部函数exampleReturned的引用 */  
  7.     return exampleReturned;  
  8. }  
  9.   
  10. var globalVar = exampleClosureForm(24);  

 
当exampleClosureForm被调用时,执行环境中所创建的函数对象(exampleReturned)不能被垃圾收集机制收集,因为它被一个全局变量(globalVar)所引用,并且可以被调用、执行。
如 : 
globalVar(N);
可能有一点复杂,被globalVar所引用的函数对象(exampleReturned)(其作用域链包含了创建它的函数——exampleClosureForm的活动对象,当然也包括全局对象)。活动对象无法被垃圾收集机制收集掉,因为globalVar所引用的函数对象(exampleReturned)需要在每次调用时,在其作用域链中添加它。
一个闭包就此形成。内部函数变量和作用链中的活动对象绑定起来作为执行环境。
活动对象被分配到globalVar的作用域链中。 globalVar现在被作为全局对象一个属性。活动对象的状态及其属性值会被保存。调用内部函数时将会解析活动对象的标识符,活动对象中具有相应标识符的属性将会被作为内部函数的属性,即使用于创建它的执行环境已经退出,这些属性的值仍然可以读取和设置。
上面的例子中,当外部函数返回时(推出其执行环境后),活动对象拥有:arg1,属性的值为2;arg2,属性的值为4;localVar,属性的值为8,并且exampleReturned是一个引用,指向从外部函数中返回的函数对象。(为了方便后面的讨论,我们将这个活动对象记为”ActOuter1”)
如果exampleClosureForm函数被再次以下面的形式调用:

 

Js代码  收藏代码
  1. var secondGlobalVar = exampleClosureForm(12, 3);   
一个新的执行环境将被创建,同时伴随一个新的活动对象,一个新的函数对象也会被返回,不同的[[scope]]属性将会是一个含有这个活动对象的作用链,这个活动对象包含:属性arg1,其属性值为12;arg2,其属性值为3。(为了方便后面的讨论,我们将这个活动对象记为”ActOuter2”)
另一个闭包通过第二次exampleClosureForm的调用就此产生。
分别将exampleClosureForm调用后产生的两个函数对象的引用赋予全局变量globalVar 和 secondGlobalVar,会返回表达式((arg1 + arg2)/(innerArg + localVar)),这个表达式针对4个标识符进行一些操作。如何解析这些标识符是闭包作用和价值所在。
考察一下globalVar所指向的函数的执行,如globalVar(2)。新的执行环境和活动对象被创建(我们把它称作”ActInner1”),它会被添加到函数执行时[[scope]]属性所指向的作用域的前端。
ActInner1提供一个称为”innerArg”的命名属性,参数值 2将会被赋予给它。
新的执行环境的作用链将是 ActInner1 -> ActOuter1 -> global object。
标识符解析依靠作用链返回表达式((arg1 + arg2)/(innerArg + localVar))完成。这些标识符的值通过检测属性决定:即逐一检查作用域链上的每一个对象,检测是否有对应名称的标识符。
作用链中的第一个活动对象是ActInner1,拥有一个innerArg的值为2的属性。其他的3个标识符可以在ActOuter1中找到:arg1的值为2,arg2的值为4,localVar的值为8.函数调用返回((2 + 4)/(2 +8)。
比较secondGlobalVar的执行,如secondGlobalVar(5)。把新的活动对象称作ActInner2,活动链为:ActInner2-> ActOuter2-> global object。ActInner2返回值为5的innerArg属性,值为12,3,8的arg1,arg2和localVar通过ActOuter2返回。最终返回的值为((12 + 3)/(5 +8)。
再一次执行secondGlobalVar,另一个新的活动对象就会出现在作用链前端,但是ActOuter2仍然是紧随最前端的多动对象,可以再一次解析到arg1, arg2 和 localVar这些值。
这就是ECMAScript内部函数如何读取,保存,访问形式参数,内部变量和它们的执行环境中的本地变量的原理。这也是为什么一个形成的,还继续存在的闭包能够让这样一个函数对象保存这些值的引用,读取和改写它们的原因。 内部对象创建时的执行环境中的活动对象,会一直在作用链之上,可以被函数对象的[[scope]]属性引用到,直到全部引用被释放并且函数对象被垃圾回收机制标识为可以进行垃圾回收。(同时包括其作用链上一些不会再用到的对象)
内部函数也可以拥有它们自己的内部函数,通过执行函数返回的内部函数形成背包也会返回内部函数并且形成它们自己的闭包。每一个嵌套的作用链获得了额外的活动对象,这些活动对象源于执行环境中内部函数的创建。ECMAScript规范要求作用链的实现是完善的,但是没有对作用链的长度加以限制。各种不同的实现可能有一些欺骗和限制,但是还没有特别严重的现象被披露出来。嵌套内部函数的潜力到目前为止超出了使用它们的人的期待。

通过闭包可以做什么
如果回答——显然可以做所有事,任何事情,似乎有些奇怪。有人告诉我闭包可以使得ECMAScript模拟一切,只需要你懂得如何实现和构思这些模拟。或许有一些难懂但是最好还是通过一些例子开始。

例1.为函数引用设置延时
闭包一个常见的作用是为一个优先于另一个函数的执行的函数提供参数。例如,当一个函数被作为setTimeout这个函数的第一个参数。
setTimeOut会调用一个作为其第一个参数的函数(也可以是一个javascript字符串形式的源代码,但是这里不加以讨论),作为其第一个参数,之后,是一个以毫秒表示的时间间隔(作为第二个参数)。如果一段Js代码使用到setTimeOut,它会调用setTimeOut函数然后传递一个函数对象的引用作为第一个参数以及一个毫秒数的间隔作为第二个参数,但是仅仅作为一个函数对象的引用,不能为其提供参数。
可以通过调用一个函数,返回其内部函数对象的引用,把这个引用传递给setTimeOut函数。内部函数执行所用到的参数会在外部函数被调用时加以返回。setTimeOut执行时避免了参数的传递,但是内部函数仍然可以访问到调用它的外部函数的参数。

Js代码  收藏代码
  1. function callLater(paramA, paramB, paramC){  
  2.     /* 返回一个匿名内部函数的引用,由一个函数表达式构成 */         
  3.    return (function(){  
  4.         /* 这个内部函数被setTimeOut调用执行;当它执行时可以读取、操作外部函数传递的参数。 */           
  5.    paramA[paramB] = paramC;  
  6.     });  
  7. }  
  8.   
  9. /* 调用函数后将返回一个内部函数的引用。传递的参数将可以被内部函数读取和使用。*/   
  10. var functRef = callLater(elStyle, "display""none");  
  11. /* 调用setTimeout函数, 传递内部函数的引用赋值给 functRef ,以functRef作为setTimeOut的第一个参数变量 
  12. */   
  13. hideMenu=setTimeout(functRef, 500);  
 

例2.通过对象实例方法关联函数
当一个函数对象的引用在未来的某些时刻可以被执行,传递参数在执行开始的时候不会被访问到,直到赋值的时候确定下来,这个特性很有用的。
为交互特别是针对DOM元素的交互封装的函数对象是一个很好的例子。它拥有doOnClick,doMouseOver,doMouseOut这些方法,当对应事件在元素上被触发时,相应的方法期望被执行。
下面例子使用一个基于关联了元素事件操作句柄的对象实例的闭包。使得事件句柄能够调用对象实例的相应方法,传递的事件对象和一个关联元素的引用将会以一个引用返回。

Js代码  收藏代码
  1. function associateObjWithEvent(obj, methodName){  
  2. /* 返回的内部函数将被作为一个DOM元素的事件句柄 */  
  3.      return (function(e){  
  4.         /* 事件对象将被传递作为e参数 */  
  5.          e = e||window.event; // 浏览器差异性  
  6.                     return obj[methodName](e, this);  
  7.     });  
  8. }  
  9. function DhtmlObject(elementId){  
  10. var el = getElementWithId(elementId);  
  11. if(el){  
  12. el.onclick = associateObjWithEvent(this"doOnClick");  
  13.                el.onmouseover = associateObjWithEvent(this"doMouseOver");  
  14.                el.onmouseout = associateObjWithEvent(this"doMouseOut");  
  15.            }  
  16. }  
  17. DhtmlObject.prototype.doOnClick = function(event, element){  
  18.     // doOnClick method body.  
  19. }  
  20. DhtmlObject.prototype.doMouseOver = function(event, element){  
  21.     // doMouseOver method body.   
  22. }  
  23. DhtmlObject.prototype.doMouseOut = function(event, element){  
  24.     // doMouseOut method body.   
  25. }  
 
DhtmlObject的任何实例不需要知道内部细节也不会破坏全局命名空间或者是与其他DhtmlObject发生冲突。
分享到:
评论

相关推荐

    JavaScript 权威指南 中文第6版 (完整目录 淘宝前端团队 译)

    书中会介绍如何通过这些方式创建和操作对象,同时还会涉及作用域和闭包的概念,它们在JavaScript编程中起着至关重要的作用。 函数是JavaScript中的重要组成部分,不仅可以作为值传递,还能作为返回值。书中会详细...

    JavaScript_Garden_CN

    `JavaScript_Garden_CN`,中文译为“JavaScript的秘密花园”,是一份详尽且不断更新的文档,专注于揭示JavaScript中的那些微妙而奇特的用法。这份文档不仅是对初学者深入学习JavaScript的宝贵资源,也是对经验丰富的...

    JavaScript模式(中文版带目录)

    在JavaScript中,由于其独特的原型继承和动态类型,理解并运用设计模式能帮助开发者编写更高效、更易于维护的代码。 二、基本技巧 这部分可能涵盖变量作用域、闭包、异步编程(如回调函数、Promise、async/await)...

    JavaScript模式

    本书由Stoyan Stefanov撰写,陈新翻译,主要针对JavaScript开发者,旨在帮助他们理解并应用各种软件设计模式来优化代码结构和提高程序效率。 ### JavaScript模式概述 #### 1. **介绍** - **作者背景**:Stoyan ...

    Javascript 权威指南 第六版 中英文

    《JavaScript权威指南》第六版是JavaScript开发者不可或缺的...无论是英文原版还是中文译版,这本书都是深入理解JavaScript的宝贵资源。通过阅读,读者不仅能掌握语言本身,还能了解其在实际开发中的应用和最佳实践。

    Eloquent-Javascript

    《Eloquent JavaScript》是一本深度探讨JavaScript编程的权威著作,其中文名译为“雄辩的JavaScript”。这本书全面覆盖了JavaScript的核心概念和技术,是初学者和经验丰富的开发者深入理解这门语言的重要资源。2021...

    Dan-justJavaScript

    6. **闭包和作用域**:理解JavaScript中的作用域规则,学习函数作用域、块级作用域,以及闭包的原理和应用场景。 7. **ES6新特性**:探索ES6(ECMAScript 2015)引入的新功能,如模板字符串、解构赋值、默认参数、...

    ITBootcamp-Live-JS-IX:Кодовисачасова

    3. **面向对象编程**:深入理解JavaScript的面向对象特性,如构造函数、原型链、继承和封装,以及ES6引入的类和模块系统。 4. **异步编程**:探讨JavaScript中的异步处理,包括回调函数、Promise、async/await,...

    拉克塔测试

    2. **原型与继承**:理解JavaScript中的原型链,如何通过构造函数、原型对象和`__proto__`属性实现对象间的继承。 3. **闭包**:学习如何创建和使用闭包,以及它们在内存管理、模块化和私有变量中的作用。 4. **...

    FE21-JS-DAY01-Julia-莱西亚

    【标题】"FE21-JS-DAY01-Julia-莱西亚" 指的是一场关于前端开发的讲座或课程,可能是某次活动的第一天,由讲师Julia(可能译为“莱西亚”)主讲,专注于JavaScript技术。这个标题暗示我们将深入探讨JavaScript的基础...

    谅解6ua:@nzakas的“ Understanding ES6”的乌克兰语翻译

    《谅解6ua》是《Understanding ES6》一书的乌克兰语译版,这本书由Nolan Zakas撰写,深入解析了ECMAScript 6(ES6)这一JavaScript编程语言的重要升级。ES6,又称为ES2015,是JavaScript语言历史上的一个里程碑,...

    fe-weekly-questions:一位专业人士,每周记录一些面试问题。

    3. **JavaScript**:前端的主要脚本语言,面试中可能包含变量、数据类型、函数、作用域、闭包、原型链、异步编程(回调、Promise、async/await)等基础知识,以及ES6及后续版本的新特性。 4. **前端框架**:React、...

Global site tag (gtag.js) - Google Analytics