`

javascript 闭包

阅读更多

写在前面的话:
试图翻译自 http://jibbering.com/faq/faq_notes/closures.html
文中大量提到《ECMA 262 》,我也没时间读这东西,可能有问题理解有误。希望纠正。
只译了前边部分,我得理解几天再继续下去。
英文水平差,凑合看吧。
国内找了半天没这篇文章中文版,献丑了就。
读后有种豁然开朗的感觉,清楚了很多javascript的问题。

一、Introduction
Closure (闭包)
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

    闭包是ECMAScript(javascript)语言强大的特征之一,如果没有真正的理解它的概念,不可能很好使用它。在一般浏览器环境中,它们很容 易被建立,但也会造成比较难理解的代码逻辑。为了避免闭包引起的缺点,利用它所提供的优点,明白它的机制是重要的。javascript语言的闭包很大程 度上依靠 scope chains(函数,变量的范围链) 和 javascript对象的灵活的属性机制 实现。
    闭包简单的解释是,ECMAScript允许inner functions(嵌套函数):函数可以定义在另外一个函数里面(关于嵌套函数可以看看<javascript权威指南>)。这些内部的函 数可以访问outer function(父函数)的local变量,参数,其它内部函数。当内部函数被构造,并可以在函数外被获得(函数当成返回值),这个内部函数被在 outer function返回后被执行(在outer函数外执行),那一个闭包形成了。(简单的理解,function被当成数据类型传递或动态执行)。 inner function还有权利访问 那些outer function(父函数)的local变量,参数,其它内部函数。那些outer function(父函数)的local变量,参数,其它内部函数在outer function返回前就有值,并返回的inner function需要改变这些值。

我估计以下代码就是一个闭包。



< script >
var g_count = 0 ;
function aaa(p) { // outer function
  var outer_count = 0 ;
  function innerfun(name,count) { // outer function中定义的另外一个inner function
     return  name + ' : ' + count + ' 次; ' ;
  }
   return  function() { // 返回一个匿名的inner函数
      var inner_count = 0 ;
             return  innerfun( ' g_count ' ,( ++ g_count)) + innerfun( ' outer_count ' ,( ++    outer_count))+innerfun('inner_count',(++inner_count))+p;
  }
}

  var fun1=aaa("fun1");
var fun2=aaa("fun2");
alert(fun1)
alert(fun1());//这时候才真正执行
  alert(fun2());
</script>
    不幸的,完全明白闭包需要知道它背后的机制和一些技术细节。
二、The Resolution of Property Names on Objects (javascript对象的属性)
    ECMAScript认可两类对象,“Native Object”和“Host Object”,Host Object属于Native Object的子类,在(ECMA 262 3rd Ed Section 4.3)中叫"Built-in Object"(内置对象)。Native objects属于语言级别,host objects被环境提供(浏览器等),例如,document objects,DOM nodes等。
    关于对象属性的存取,数据类型,原型对象prototype的使用,我这就不译了。
    可以参见我的另一篇文章

三、Identifier Resolution, Execution Contexts and Scope Chains
1、The Execution Context
    执行环境上下文(Execution Context)是个抽象的概念,the ECMSScript specification (ECMA 262 3rd edition) to define the behaviour required of ECMAScript implementations。规范没有说 execution contexts 应该怎样实现,但规范中提到execution contexts是个关联属性结构,因此你可以假想为一个有属性的对象,但不是公有的(public)。
    所有的javascript代码在一个execution context中执行。Global代码(.js文件中)在我叫做globla execution context中执行,每个函数的调用有个专属的execution context。注意,用eval函数执行的代码有个独特的execution context.(原文中说eval函数没常被程序员应用,确实如果掌握闭包使用后,还是可以避免一些eval使用的)。在section 10.2 of ECMA 262 (3rd edition)中详细讲述的execution context.
    当一个函数被调用,那相应的execution context被建立,如果另外的函数(或同一个函数递归调用),那新的execution context被建立,直到函数return(对于递归调用,execution context是独立的)。因此,javascript代码的执行会建立很多的execution contexts.
    当一个函数的execution context被建立(javascript中有global和function两种,eval没讨论),按照顺序,有几个事情要发生。
   (1)在一个函数的execution context中,一个"Activation"对象被建立(我在其它文章中叫调用对象)。the activation被另外规范解释。你可以把它当成一个对象,因为它有对象的属性,但它不是一般对象,它没有原型对象,并不能被javascript代 码直接引用。
   (2)建立一个arguments对象,它和数组类似,以整数为索引来访问值,表示函数的参数。它有length和callee属性。这个arguments对象被当成activation对象的属性。在函数内可以直接访问得到。
   (3)下一步,execution context被分配一个 scope属性(scope chain后面讲到,我们可以把scope理解成对象的一个scope属性,值是scope chain)。一个scope由一列对象组成 (或叫chain)。每个函数对象也有由chain组成的scope属性。函数的scope=Activation object+上级对象的scope的属性.(这里的scope可以理解成servlet中的chain,一系列请求组 成的链。)
   (4)Activation object的实例化。Activation object(调用对象)可以看作Variable(变量)。
function fun(a,b){};fun('p'); a和b会当成调用对象的属性,但函数调用是参数不够,b的值为undefined。如果函数内有inner function,当成属性赋值给调用对象。变量实例化最后把local variables(函数内部声名的变量) 当成调用对象的参数。 调用对象的属性 包括函数的参数、内部变量。
   (5)在函数内,local variables作为调用对象的属性出现,function (a){alert(s);   var s='a';}调用时,s的值是unidefine,直到执行到赋值语句后才有值。
   (6)arguments属性是以索引标识的参数,它和显示声明的参数是重复的,值也相同。如果local变量的签名和参数相同,那么它们三者一个变化,其它都会相应改变值。见下例
function a(p){alert( arguments [0]);alert(p);var p=1;alert(p);alert( arguments [0]);};a(0);
   (7)最后,为this关键字设置值。可能 new Function()的有些疑问。关于this关键字,感觉自己还没有彻底理解。this关键字关联于执行时的作用域,而非定义时的作用域。(The this keyword is relative to the execution context, not the declaration context )

    global execution context 的过程和上面有些不同,它没有arguments也不需要定义Activation object。global execution context也不需要scope chain,因为scope chain只有一个,就是global object.它的变量实例化过程和inner function其实都是根变量和函数,就是global对象的属性。global execution context用this应用global对象,在浏览器中为window.

2、Scope chains and [[scope]]
    The scope chain of the execution context for a function call is constructed by adding the execution context's Activation/Variable object to the front of the scope chain held in the function object's [[scope]] property。我理解每个函数执行环境都有scope chain,子函数(inner function)的scope chain包括它的父函数的scope chain,如此递归对global对象。
    在ECMAScript中,函数是个对象,它们可以用function声明,或function表达式声明,或Function构造函数初始化。
    用Function构造的函数对象一直有个scope属性,指向的scope chain 仅包括 global 对象。
    用function表达式定义的函数对象,这类函数对象的scope chain被分配到内部的scope 属性。

(1)简单的global函数,例如:-
function exampleFunction(formalParameter){
    ...   // function body code
}

在global execution context的变量实例化阶段,the corresponding function object 被创建。global execution context有scope chain,只包含global object.因此,函数对象被分配一个指向global object的 scope属性( internal [[scope]] property)。

(2)A similar scope chain is assigned when a function expression is executed in the global context:-

var exampleFuncRef = function(){
    ...   // function body code
}

这个例子scope chain情形与上类似。有个区别是函数对象在代码执行过程才创建。(见我以前文章)
(3)inner 函数的情形较为复杂,看下面代码:

function exampleOuterFunction(formalParameter){
    function exampleInnerFuncitonDec(){
        ... // inner function body
    }
    ...  // the rest of the outer function body.
}
exampleOuterFunction( 5 );

    outer函数在global execution context变量实例化阶段被创建,因此它的scope chain只包括global object.
   当global代码执行到调用exampleOuterFunction时,一个新的execution context被创建,(Activation)调用对象也被创建。这个新的execution context的scope chain由两部分组成,新的调用对象在顶层,outer函数scope chain(只包括global object)在后。新的execution context的变量实例化阶段(outer 函数体内)导致inner函数对象被创建,这个inner函数对象的[[scope]] property 被指向上述的哪个scope chain,也就是调用对象和global object.注意inner function也有调用对象。

引用了 http://wj.cnblogs.com/archive/2006/04/22/381851.html 回复内的代码

<SCRIPT LANGUAGE="JavaScript">
<!--
//global 代码实例化阶段,它知道global object.
function createAClosure()
{
//当调用时,调用对象创建,execution context的scope chain 包括调用对象和global
//object.
var local = 0;
return function(){return ++local;}; //这个inner function 的scope //chain持有
//createAClosure的调用对象,所以也持有local的值
}
var c1 = createAClosure(); //调用对象和global object
var c2 = createAClosure(); //另外一个调用对象和global object
document.write(c1() + "<br/>");
document.write(c1() + "<br/>");
document.write(c1() + "<br/>");
document.write(c2() + "<br/>");
document.write(c2() + "<br/>");
//-->
</SCRIPT>

以上所有过程自动进行,代码不需要任何设置(造成很多人不知道闭包原因)。
scope chain 简单看来可以按照下面的代码来描述:

函数体外Execution context 的scope chain  只有 global.
function fun(){
函数体内Execution context 的scope chain  fun的调用对象+global
    function innerfun(){
      inner函数体内Execution context 的scope chain innerfun的调用对象 + fun的调用对象 + global
    }

}

但是ECMAScript提供的with表达式会修改scope chain.with表达式,我是能不用就不用了,<javascript权威指南>中也说with会造成性能的集聚下降。原文贴在下面。有时间再仔细研究。

The with statement evaluates an expression and if that expression is an object it is added to the scope chain of the current execution context (in front of the Activation/Variable object). The with statement then executes another statement (that may itself be a block statement) and then restores the execution context's scope chain to what it was before.

A function declaration could not be affected by a with statement as they result in the creation of function objects during variable instantiation, but a function expression can be evaluated inside a with statement:-

/* create a global variable - y - that refers to an object:- */
var y = {x:5}; // object literal with an - x - property
function exampleFuncWith(){
    var z;
    /* Add the object referred to by the global variable - y - to the
       front of he scope chain:-
    */
    with(y){
        /* evaluate a function expression to create a function object
           and assign a reference to that function object to the local
           variable - z - :-
        */
        z = function(){
            ... // inner function expression body;
        }
    }
    ...
}

/* execute the - exampleFuncWith - function:- */
exampleFuncWith();
When the exampleFuncWith function is called the resulting execution context has a scope chain consisting of its Activation object followed by the global object. The execution of the with statement adds the object referred to by the global variable y to the front of that scope chain during the evaluation of the function expression. The function object created by the evaluation of the function expression is assigned a [[scope]] property that corresponds with the scope of the execution context in which it is created. A scope chain consisting of object y followed by the Activation object from the execution context of the outer function call, followed by the global object.

When the block statement associated with the with statement terminates the scope of the execution context is restored (the y object is removed), but the function object has been created at that point and its [[scope]] property assigned a reference to a scope chain with the y object at its head.

3、Identifier Resolution
   关于这部分我决定不按照原文直译。Identifier Resolution是一个过程,而不是具体的概念,我举个例子可能就明白了。

<SCRIPT LANGUAGE="JavaScript">
<!--
var s_global='global';//scope chain {global} 中
var s_outer='global';//scope chain {global} 中
var s_inner='global';//scope chain {global} 中
function outerfun(){//scope chain {global} 中
    var s_outer='outer';//scope chain  {outerfun调用对象,global}
pf('outer代码开始');
pf(s_global);//global
    pf(s_outer);//outerfun调用对象
    pf(s_inner);//global

function innerfun(){////scope chain  {outerfun调用对象,global}
    var s_inner='inner';//scope chain  {innerfun调用对象,outerfun调用对象,global}
pf('inner代码开始');
pf(s_global);//global
    pf(s_outer);//outerfun调用对象
    pf(s_inner);//innerfun调用对象
}
return innerfun;
}
function pf(msg){document.writeln('</br>'+msg);};
pf('global代码开始');
pf(s_global);//global
pf(s_outer);//global
pf(s_inner);//global

var a=outerfun();
a();
pf('第二个函数开始------------------------');
var b=outerfun();
b();
//-->
</SCRIPT>

其 实Identifier Resolution就是属性查找的过程。 先从scope chain 的第一个对象开始找,如果找不到再从scope chain的第二个对象找, global对象始终是scope chain 的最后一个对象,如果global object中也找不到属性,那为undefined.
有两个注意点:
   如果可能,这个查找过程会对对象的prototype(原型对象)查找。先找实例属性,再找原型属性。见我的其它文章。
   在函数内,这个函数的调用对象包括的参数,local变量,inner函数等。

 

分享到:
评论

相关推荐

    JavaScript闭包

    Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态...本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点 ECMAScript语言规范来使读者可以更深入的理解闭包。

    JavaScript闭包研究及典型应用.pdf

    JavaScript 闭包研究及典型应用 JavaScript 闭包是一种强大的技术,能够在各种场景中发挥重要作用。本文将介绍 JavaScript 闭包的定义、使用场景和典型应用。 闭包函数的定义和使用场景 在 JavaScript 语言中,...

    基于JavaScript闭包的Web图片浏览控件的实现.pdf

    基于JavaScript闭包的Web图片浏览控件的实现 本文主要讲解了基于JavaScript闭包原理的Web图片浏览控件的实现,包括JavaScript闭包概念、闭包应用场景、Web图片浏览控件的设计思路和实现方法。 1. JavaScript闭包...

    javascript闭包详解中文word版

    资源名称:javascript闭包详解 中文word版   内容简介: Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C 的程序员来说是一个新的...

    Javascript 闭包完整解释

    ### JavaScript闭包完整解释 #### 一、闭包的基本概念 **闭包**是一个非常重要的JavaScript概念,它指的是一个函数能够记住并访问其外部作用域中的变量的能力,即使该函数在其外部作用域之外被调用也是如此。具体...

    JavaScript知识点总结(十六)之Javascript闭包(Closure)代码详解

    在深入讨论JavaScript闭包之前,首先需要了解JavaScript的变量作用域。在JavaScript中,变量的作用域分为两种:全局变量和局部变量。全局变量是在函数外部定义的变量,可以在JavaScript程序的任何地方被访问。局部...

    javascript闭包的理解

    标题《JavaScript闭包的理解》涉及的知识点主要围绕JavaScript编程中的一个重要概念——闭包。闭包是一个高级且复杂的话题,它是JavaScript语言的核心特性之一,同时也是一大难点。要想熟练运用JavaScript,掌握闭包...

    javascript 闭包实例下载

    JavaScript 闭包是一种高级编程概念,它在JavaScript中扮演着至关重要的角色,特别是在函数式编程和模块化设计中。闭包本质上是函数和其能够访问...通过学习这些实例,你将能够更好地掌握JavaScript闭包这一核心概念。

    javaScript闭包技术资料

    ### JavaScript闭包技术详解 #### 一、闭包的基本概念 **闭包**是JavaScript中一个重要的概念,它涉及到函数的执行环境、作用域链等关键要素。简单来说,闭包是一个函数及其相关的引用环境的组合。具体而言,当一...

    【JavaScript源代码】详解JavaScript闭包问题.docx

    详解JavaScript闭包问题  闭包是纯函数式编程语言的传统特性之一。通过将闭包视为核心语言构件的组成部分,JavaScript语言展示了其与函数式编程语言的紧密联系。由于能够简化复杂的操作,闭包在主流JavaScript库...

    javascript闭包高级教程

    ### JavaScript闭包高级教程 #### 简介 在JavaScript编程中,“闭包”是一个非常重要的概念,尤其对于希望深入理解和高效使用JavaScript的开发者来说。简单地说,闭包是一种能够记住并访问其创建时周围环境的函数...

    javascript闭包的高级使用方法实例

    JavaScript 闭包是一种强大的特性,它允许函数访问和操作其外部作用域中的变量,即使在外部函数执行完毕后,这些变量仍然保持活动状态。在高级使用中,闭包可以用于实现模块化、数据封装、方法扩展和重载、以及创建...

    揭开Javascript闭包的真实面目

    【JavaScript 闭包详解】 闭包是JavaScript编程中一个核心且关键的概念,尤其对于初学者而言,理解起来可能有些挑战。闭包本质上是一种特殊的作用域,它可以捕获并存储其外部函数作用域内的变量,即使外部函数已经...

    javascript闭包

    ### JavaScript闭包详解 #### 一、闭包概念与工作机制 **闭包**是JavaScript中最强大的特性之一,它使得函数能够记住并访问其定义时所在的作用域中的变量。要理解和运用闭包,首先需要理解作用域、作用域链以及...

    javaScript闭包的理解

    ### JavaScript闭包的理解 #### 一、闭包的定义与特点 闭包是JavaScript中一个非常重要的概念,它指的是一个函数能够访问并操作其外部作用域中的变量的能力。这一特性使得JavaScript具有了一些其他语言不具备的...

    javaScript闭包

    ### JavaScript闭包详解 #### 引言 JavaScript作为一种动态、弱类型的编程语言,在Web开发领域占据了举足轻重的地位。其中,“闭包”是JS语言中一个非常重要的概念,它不仅能够帮助开发者实现某些特殊的功能,如...

    JavaScript闭包(closure).pdf

    理解并掌握JavaScript闭包是成为专业前端开发者的关键一步。在实际开发中,合理利用闭包可以提高代码的复用性和可维护性,同时也能避免一些常见的编程陷阱。通过深入学习和实践,可以更好地运用闭包这一强大的工具来...

Global site tag (gtag.js) - Google Analytics