JavaScript的闭包问题
JavaScript是一种非常强大的函数式编程语言,可以动态创建函数对象。
由于JavaScript还支持闭包(Closure),因此,函数可以引用其作用域外的变量,非常强大。
来看看在JavaScript中使用闭包的陷阱:
var tasks = []; for (var i=0; i<3; i++) { tasks.push(function() { console.log('>>> ' + i); }); } console.log('end for.'); for (var j=0; j<tasks.length; j++) { tasks[j](); }
如果在循环中创建函数,并引用循环变量,原意是打印出0,1,2,但结果却是一样的:
end for. >>> 3 >>> 3 >>> 3
这个问题的原因在于,函数创建时并未执行,所以先打印end for.,然后才执行函数。
由于函数引用了循环变量i,在函数执行时,由于i的值已经变成了3,所以,打印出的结果不对。
如果我们用一个变量n来复制一份i传入呢?
var tasks = []; for (var i=0; i<3; i++) { var n = i; tasks.push(function() { console.log('>>> ' + n); }); } console.log('end for.'); for (var j=0; j<tasks.length; j++) { tasks[j](); }
结果还是不对:
end for. >>> 2 >>> 2 >>> 2
注意到i会比n多加一次,所以n的最终值是2。
这个问题的原因是JavaScript虽然有局部变量,但局部变量只有函数作用域,而没有其他语言通常有的块作用域(循环内部,if内部),所以,无论在函数内部的哪个地方定义变量,变量作用域都是整个函数。
把上述代码的变量提取到函数开头,其实是这样的:
var tasks, i, n, j; tasks = []; for (i=0; i<3; i++) { n = i; tasks.push(function() { console.log('>>> ' + n); }); } console.log('end for.'); for (j=0; j<tasks.length; j++) { tasks[j](); }
完全等价。
根据道爷的说法,JavaScript缺少块作用域是语言设计的一个缺陷。所以,上面的代码有问题,就是因为n的作用域是整个函数,而不是循环。
创建闭包的一条原则就是:
不要引用循环变量!
这条原则对有没有块作用域的函数式编程语言都适用。
如果一定要在闭包中引用循环变量怎么办???
方法是再创建一个函数,将循环变量作为函数参数传入:
var tasks = []; for (var i=0; i<3; i++) { var fn = function(n) { tasks.push(function() { console.log('>>> ' + n); }); }; fn(i); }
这段代码是正确的,是因为循环内部,fn()函数立即被执行,因此,闭包拿到的参数n就是当前循环变量的值的副本。
最后,简化语法,直接用匿名函数的立即执行模式(function() { ... })()改写如下:
var tasks = []; for (var i=0; i<3; i++) { (function(n) { tasks.push(function() { console.log('>>> ' + n); }); })(i); }
建议继续学习:
- for 循环为何可恨? (阅读:4504)
- 理解Javascript的闭包 (阅读:3833)
- GC与JS内存泄露 (阅读:3527)
- 闭包漫谈(从抽象代数及函数式编程角度) (阅读:3185)
- 在回调和闭包中的内存泄漏 (阅读:3099)
- 什么是闭包(Closure)? (阅读:2915)
- 闭包与作用域 (阅读:2881)
- 前端代码之丑(一):分支化技巧 (阅读:2821)
- 闭包漫谈(从抽象代数及函数式编程角度) (阅读:2762)
- Perl闭包实例解释 (阅读:2690)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:廖雪峰 来源: 廖雪峰的官方网站
- 标签: 闭包
- 发布时间:2015-01-23 23:49:11
- [69] IOS安全–浅谈关于IOS加固的几种方法
- [66] 如何拿下简短的域名
- [66] Twitter/微博客的学习摘要
- [63] android 开发入门
- [60] find命令的一点注意事项
- [60] Go Reflect 性能
- [59] Oracle MTS模式下 进程地址与会话信
- [58] 图书馆的世界纪录
- [58] 流程管理与用户研究
- [56] 【社会化设计】自我(self)部分――欢迎区