书接上回,继续闭包。

Closures

1、自动的垃圾回收
   ECMAScript有自动的垃圾回收机制。与java类似。但是规范也没有对该机制详细定义,而是让浏览器等规范实现厂家来实现,各种浏览器实现不一样,垃圾回收的算法也不同。好象ie的实现会出现内存溢出问题。对我我们来说注意到这点就够了,后面会提到如何避免ie的这个bug.
   关于上篇提到的execution context,调用对象,参数,scope chain 等等都需要内存,垃圾回收机制会在适当时候释放内存。
2、闭包如何形成
   通俗的说,当一个(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个数量级的原因。
3、闭包能做什么(例子)
(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 = [
        '<div id="',
        '',   //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;\"><img src=\"',
        '',   //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中的闭包,代码偶合性会更低。