一, 闭包
当函数可以记住并访问所在的词法作用域时, 就产生了闭包, 即使函数是在当前词法作用域之外执行.
- function foo(){
- var a = 2;
- function bar(){
- console.log(a) // 2
- }
- bar()
- }
上面的是闭包吗?
确切的说并不是, 在上面的代码中, 函数 bar 可以访问 foo 作用域以及全局作用域, 因为 bar 嵌套在函数内部, 但是这个并不是一个闭包.
- function foo(){
- var a = 2;
- function bar(){
- console.log(a) // 2
- }
- return bar()
- }
- var baz = foo();
- baz(); // 2 这个才是闭包
函数 bar() 的词法作用域能够访问 foo 的内部作用域, 我们把函数 bar() 作为 foo 的返回值, 在 foo 执行之后, 其返回值赋值给变量 baz, 调用 baz, 实际上就是通过不同的标识符调用了内部函数 bar,bar 可以被正常执行.
在 foo 执行之后, 通常会期待 foo 的整个内部作用域被销毁, 因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间, 而闭包的神奇之处就在于阻止这样的事情发生. 因为 bar 本身还在使用 foo 内部的变量, 使得 foo 的作用域一直存在, 一共 bar 在之后任何时间进行引用.
bar 依然持有该作用域的引用, 而这个引用就叫做闭包.
即这个函数在定义时的词法作用域以外的地方被调用, 闭包使得函数可以继续访问定义时的词法作用域.
- function foo(){
- var a = 2;
- function bar(){
- console.log(a) // 2
- }
- baz(bar)
- }
- function baz(fn){
- fn() // 闭包
- }
无论通过任何手段将内部函数传递到所在的词法作用域以外, 它都会持有对原始定义作用域的引用, 无论在何处执行这个函数都会使用闭包.
二, 循环与闭包
- for(var i = 1; i <= 5 ; i++){
- setTimeout(function timer(){
- console.log(i)
- },i*1000)
- }
这段代码估计很多人面试的时候都见过, 那你是怎么回答的呢?
延迟函数的回调在循环结束后才执行. 我们试图假设循环中的每个迭代都会在运行时候给自己'捕获'一个 i 的副本, 但是根据作用域的工作原理, 他们都在同一个作用域里, 也就是说只有一个 i, 循环结束后是 6, 那么延迟函数执行后获取到的 i 就是 6.
- for(var i = 1; i <=5 ; i++){
- (function(){
- setTimeout(function timer(){
- console.log(i)
- },i*1000)
- })()
- }
这样行吗?
当然不行, 因为现在虽然后更多的词法作用域, 每个延迟函数都会将 IIFE 每次迭代中创建的作用域封闭起来, 但是此时作用域中是空的, 并没有 i, 所以还是都打印出 6.
- for(var i = 1; i <=5 ; i++){
- (function(i){
- setTimeout(function timer(){
- console.log(i)
- },i*1000)
- })(i)
- }
这样就可以了. 问题解决.
其实闭包还有很多用图, 甚至经常看到闭包, 通过这篇文章, 能够认识闭包就是一种进步.
来源: http://www.jianshu.com/p/b1f24fdb0876