javascript语言中的闭包

1、Introduction javascript

Closure (闭包)html

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). java

    闭包是ECMAScript(javascript)语言强大的特征之一,若是没有真正的理解它的概念,不可能很好使用它。在通常浏览器环境中,它们很容易被创建,但也会形成比较难理解的代码逻辑。为了不闭包引发的缺点,利用它所提供的优势,明白它的机制是重要的。javascript语言的闭包很大程度上依靠 scope chains(函数,变量的范围链) 和 javascript对象的灵活的属性机制 实现。node

    闭包简单的解释是,ECMAScript容许inner functions(嵌套函数):函数能够定义在另一个函数里面(关于嵌套函数能够看看 )。这些内部的函数能够访问outer function(父函数)的local变量,参数,其它内部函数。当内部函数被构造,并能够在函数外被得到(函数当成返回值),这个内部函数被在 outer function返回后被执行(在outer函数外执行),那一个闭包造成了。(简单的理解,function被当成数据类型传递或动态执行)。 inner function还有权利访问 那些outer function(父函数)的local变量,参数,其它内部函数。那些outer function(父函数)的local变量,参数,其它内部函数在outer function返回前就有值,并返回的inner function须要改变这些值。 程序员

我估计如下代码就是一个闭包。算法

 

< script > express

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());

    不幸的,彻底明白闭包须要知道它背后的机制和一些技术细节。

2、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的使用,我这就不译了。

    能够参见个人另外一篇文章

3、Identifier Resolution, Execution Contexts and Scope Chains

一、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.

二、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 回复内的代码

以上全部过程自动进行,代码不须要任何设置(形成不少人不知道闭包缘由)。

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表达式,我是能不用就不用了, 中也说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.

三、Identifier Resolution

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

其实Identifier Resolution就是属性查找的过程。 先从scope chain 的第一个对象开始找,若是找不到再从scope chain的第二个对象找, global对象始终是scope chain 的最后一个对象,若是global object中也找不到属性,那为undefined.

有两个注意点:

   若是可能,这个查找过程会对对象的prototype(原型对象)查找。先找实例属性,再找原型属性。见个人其它文章

   在函数内,这个函数的调用对象包括的参数,local变量,inner函数等。

若是有对javascript语言感兴趣的,欢迎交流批评。

http://www.blogjava.net/zkjbeyond/category/10156.html

参考:

    《javascript权威指南》

     http://jibbering.com/faq/faq_notes/closures.html

书接上回,继续闭包。

Closures

一、自动的垃圾回收

   ECMAScript有自动的垃圾回收机制。与java相似。可是规范也没有对该机制详细定义,而是让浏览器等规范实现厂家来实现,各类浏览器实现不同,垃圾回收的算法也不一样。好象ie的实现会出现内存溢出问题。对我咱们来讲注意到这点就够了,后面会提到如何避免ie的这个bug.

   关于上篇提到的execution context,调用对象,参数,scope chain 等等都须要内存,垃圾回收机制会在适当时候释放内存。

二、闭包如何造成

   通俗的说,当一个(outer)函数的返回类型是(inner)函数类型时,这个被返回的inner函数斥又outer函数的scope chain,这时候闭包造成。  以下例:

function exampleClosureForm(arg1, arg2){

    var localVar = 8;

    function exampleReturned(innerArg){

        return ((arg1 + arg2)/(innerArg + localVar));

    }

    /* return a reference to the inner function defined as -

       exampleReturned -:-

    */

    return exampleReturned;

}

var globalVar = exampleClosureForm(2, 4);

  如今exampleClosureForm(2, 4)返回的inner函数不能被垃圾回收,由于它被变量globalVar持有,并可执行globalVar(n)。

  可是内部的原理没有表面上那么简单。如今globalVar是个函数对象,它的[[scope]] property 指向一个scope chain,而这个scope chain 包括   exampleClosureForm函数的调用对象+global对象。因此垃圾回收不能回收这部份内存。

  一个闭包造成了。inner函数对象有本身的变量,也能够访问exampleClosureForm函数调用过程当中的参数,local变量等。

  在上面的例子中,globalVar(n)执行时,在经过调用对象能够访问到exampleClosureForm(2, 4)执行过程当中的参数,local变量等。arg1 = 2,arg2 = 4 ,localVar=8,这些属性都经过调用对象"ActOuter1"能够获得。

若是增长如下代码,又返回另一个inner 函数。

var secondGlobalVar = exampleClosureForm(12, 3);

exampleClosureForm(12, 3)会引发新的调用对象建立,咱们定义为ActOuter2。这个过程当中,arg1 = 12,arg2 = 3 ,localVar=8。第二个闭包造成了.

   下面考虑返回的inner函数执行过程。如globalVar(2)。新的execution context、调用对象(ActInner)被建立。如今的scope chain是  ActInner1->ActOuter1->global object.  函数返回是 ((2 + 4)/(2 + 8)).

    若是是secondGlobalVar(5)被执行状况是什么呢?如今的scope chain是ActInner2-> ActOuter2-> global object.函数返回是 ((12 + 3)/(5 + 8)).

    经过比较,这两个inner函数互不干扰的执行。若是嵌套更多的函数的话,与上面所诉相似。明白的javascript的闭包,从这个方面可能就能体会到它比java等慢n个数量级的缘由。

三、闭包能作什么(例子)

(1)

function callLater(paramA, paramB, paramC){

    return (function(){

        paramA[paramB] = paramC;

    });

}

var functRef = callLater(elStyle, "display", "none");

hideMenu=setTimeout(functRef, 500);

想象咱们作颜色渐变,或者动画的时候吧。上面提供的函数多幽雅。

(2)

function associateObjWithEvent(obj, methodName){

    return (function(e){

        e = e||window.event;

        return obj[methodName](e, this);

    });

}

function DhtmlObject(elementId){

    var el = getElementWithId(elementId);

    if(el){

        el.onclick = associateObjWithEvent(this, "doOnClick");

        el.onmouseover = associateObjWithEvent(this, "doMouseOver");

        el.onmouseout = associateObjWithEvent(this, "doMouseOut");

    }

}

DhtmlObject.prototype.doOnClick = function(event, element){

    ... // doOnClick method body.

}

DhtmlObject.prototype.doMouseOver = function(event, element){

    ... // doMouseOver method body.

}

DhtmlObject.prototype.doMouseOut = function(event, element){

    ... // doMouseOut method body.

}

......

又一种注册事件的方法。我以为做者的这种实现可谓精妙。大大的开阔了个人思路。咱们能够为咱们的UI事件绑定到对象上,能够很好的重用代码。另外比起prototype.js的时间注册来讲简单点。

(3)

var getImgInPositionedDivHtml = (function(){

    var buffAr = [

        '

        '',   //index 1, DIV ID attribute

        '" style="position:absolute;top:',

        '',   //index 3, DIV top position

        'px;left:',

        '',   //index 5, DIV left position

        'px;width:',

        '',   //index 7, DIV width

        'px;height:',

        '',   //index 9, DIV height

        'px;overflow:hidden;\">

        '',   //index 11, IMG URL

        '\" width=\"',

        '',   //index 13, IMG width

        '\" height=\"',

        '',   //index 15, IMG height

        '\" alt=\"',

        '',   //index 17, IMG alt text

        '\"><\/div>'

    ];

    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('');

    }); //:End of inner function expression.

})();

这种匿名函数的调用在dojo中见过,如今再看,感受不同。

以上是原做者的例子,我抄过来的。下次我准备深刻研究一下闭包能给咱们开发js类库提供什么更好的思路。感受如今不少人对闭包了解很少,通过这段时间的思考,利用javascript中的闭包,代码偶合性会更低。

相关文章
相关标签/搜索