javascript Closure 基本概念

javascript中的闭包(和数学中的闭包是不一样的),这个概念这里就不详细说了,网上有一堆堆的文章来讲述什么是闭包。而今天,不在这里讲述概念问题,因为概念是一个 可以很简单描述也可以很复杂描述的东西,而且有时候可能越描述的详细就越让人迷糊。笔者也是如此,之前看了很多这类文章,时而清楚,时而糊涂。 今天我们撇开概念,其实闭包这个东西真没什么好讲的,平时如果用不到呢,就别去故意构造闭包把代码搞得自己都看不懂了,这和乱用设计模式是一个做法(以上只是一家之言,如果你是 高手,而且非常了解语言的底层实现或者不在乎别人看得懂看不懂就当我之前啥都没说过,哈哈哈)。不知不觉已经讲了那么多废话了,嘿嘿,马上回来。 今天,要说的其实很简单,就如本文的题目一样,是一次实践。这个实践在下文我就会给出,当然这个实践是实践项目中遇到的问题,不过为了 简化逻辑,还是有些修改的,因为实际项目中的一些业务就没必要添加进来凑热闹了,我们只关注闭包是如何巧妙的解决问题的。

###问题描述 如下代码:

<input type="button" value="load_finish" id="loaded" />
    <script type="text/javascript">

        var plugin = {
            load : function(ctx){
                var btn = document.getElementById('loaded');
                    //为了简化模型,
                    //这里用按钮点击事件监听模拟加载完毕的事件
                    //实际可能是异步加载其它资源的完成事件监听
                    //当然有些情况确实是对于dom元素的事件监听,那就确实是这个样子的。
                    addEvent(btn,'click',function(){
                        //加载插件完成后触发回调
                        ctx.finish();
                    });

            }

        };
        //客户端调用插件
        for(var i = 0;i < 5;i++){
            plugin.load({
                finish : function(){
                    //编写回调函数,由于某种需求,我们需要
                    //知道循环每次变量i的值
                    alert('load finished' + i);
                }
            });

        }

        //辅助函数
        function addEvent(elem,type,fn){
            if(document.addEventListener){
                elem.addEventListener(type,fn,false);
            }else if(document.attachEvent){
                elem.attachEvent('on' + type,fn);
            }else{
                elem['on'+type] = fn();
            }
        }
</script>

但是悲剧的事情是,运行程序后,发现每次的i打印的都是5,这可不是我们想要的结果。为什么会这样呢?根据闭包的定义, addEvent(btn,‘click’,function(){})中的匿名函数引用了ctx这个外部函数load(ctx)的局部变量,因此 每个addEvent(btn,‘click’,function(){})中的匿名函数的作用域链中都保存着load函数的活动对象,且随着load函数的每次执行,活动对象 不会被销毁。而ctx每次在load结束后就会被销毁,在ctx中又引用了外部作用域的i,最后i变成了5,那最后的ctx中的i就是5。 从另一个角度来看,让我们再来到finish函数执行的时候,查看此时它的作用域链, 我们发现一个是他自己的变量对象,然后是ctx对象的变量对象,然后还有外部的变量i所在环境的变量对象。然后当执行的时候在自己变量对象查找 i,没找到,往上在ctx中发现之前load函数执行时候,引用了ctx,而ctx引用了i,因此找到了,而通过前面分析ctx是同一个,因此i最后都是5了。 那么,这个怎么破?。

###使用闭包解决问题

//客户端调用插件
for(var i = 0;i < 5;i++){
    (function(num){
        plugin.load({
                finish : function(){
                     //编写回调函数,由于某种需求,我们需要
                     //知道循环每次变量i的值
                        alert('load finished' + num);
                    }
                  });
           })(i);
}

其它不变,修改客户端调用的地方,这里,我们增加了一个自执行的匿名函数。然后传入参数i,这样当里面的回调函数中引用的就变成了 匿名函数的局部变量num,而num其实就是每次循环i赋值过来的。现在在执行finish时候变成查找num了。原来每次循环load函数执行后i变量被ctx引用 后本次i就销毁了。最后一次i变成5给ctx引用。而经过修改的代码中,当引用num时候num是匿名自执行函数中的变量,形成闭包,因此每次load执行,ctx对象将继续在内存中,不会被销毁,最后就有多个ctx的引用,每个ctx保存着每次的i值。因此就能输出正常结果了。

本篇小结

本以为是一个有趣的闭包的问题,尼玛刚开始写以为很简单,越写越觉得是个大坑,结果,硬生生的去填了,最后发现有好多问题,由于水平有限, 以上文章很多地方未实际验证,分析过程不一定正确,但是解决方案没问题,确实实现了效果。如有更好办法或者哪里有问题,不惜赐教。

##文档信息