再谈Js闭包

本篇文章是JavaScript闭包实例讲解的扩展与延伸,上文提到的本文不再进行赘述。

不知不觉距离上一篇闭包文章已经过了8个月了,现在的理解对比之前要健壮的多,再次总结下花生理解的闭包。

闭包实际上就是子作用域读取父作用域的变量,这本来很合理也很简单,但是关键点在于这个读取是动态的,请看下面的例子:

for(var i=0 ;i<3 ;i++){
    setTimeout(function(){
        console.log(i);
    });
};
// 输出 3 3 3

结果并不是期望的0 1 2,因为是动态的读取i,因此如果你在下文改变变量i也依旧会影响到输出读取i。

传统的解决方案是构建闭包,是最有效也是兼容性最好的方法

for(var i=0 ;i<3 ;i++){
    (function(num){
        setTimeout(function(){
            console.log(num);
        });
    })(i);
};
// 输出 0 1 2

这么做是十分有效的,为每一个闭包单独创建一个作用域,也是下面要说的其他解决方案的基础。

这一段代码很重要,理解这一段代码基本上就可以说理解闭包了。

实际上,大多数情况我们并不是想单纯是使用for循环,for循环的一个很常见的用处是遍历数组。

var arr =['a' ,'b' ,'c'];
for(var i=0 ;i<arr.length ;i++){
    setTimeout(function(){
        console.log(arr[i]);
    });
};
// 输出 undefined undefined undefined

因为是动态读取,所以输出undefined很正常。可以使用上面的构建一个自执行函数来解决,但还有一个更方便的解决方案,也是实际开发中经常用到的。

['a' ,'b' ,'c'].forEach(function(item){
    setTimeout(function(){
        console.log(item);
    });
});
// 输出 a b c

利用Array原生的forEach可以更好的实现,而且也符合语义,这个是花生最推荐的用法。

如果浏览器较新支持ES5,Function还提供一个bind方法来绑定参数

for(var i=0 ;i<3 ;i++){
    setTimeout(console.log.bind(null ,i));
};
// 输出 0 1 2

Function.bind具体的语法与兼容性可以参考MDN

还有其他的“歪门邪道”的解决方案,比如利用闭包读取到父作用域的集合,在集合里寻找“自己”,或者是利用js的引用传递等等。

实际上,利用ES5的bind方法和Array的forEach就已经可以解决所有问题了,所以在实际开发中应该避免第一种构建闭包的解决方案。

发表新的回复