闭包是js中一个重要但对小白来说又是很难搞懂的一个概念。希望这篇文章可以给那些像我一样还是小白的同学一个入门,先给出闭包的定义:
** 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。**
先来看一个小李子:
function foo() {
vara = 2;
return function bar() {
console.log(a)
}
}
var baz = foo();
baz();//2
上面例子中,函数<code>bar()</code>的词法作用域访问了函数<code>foo()</code>的内部作用域,在<code>foo()</code>执行后,其返回值(也就是内部的函数<code>bar()</code>)赋给<code>baz()</code>。打印出<code>baz</code>如下:
我们会认为,当函数<code>foo()</code>执行完成之后,其内部作用域应该被销毁了,但接下来的函数<code>baz()</code>为什么依然访问了<code>foo()</code>的内部变量<code>a</code>呢?
实际上,函数<code>bar()</code>的是在<code>foo()</code>内部声明的,<code>bar()</code>也就拥有涵盖<code>foo</code>作用域的闭包,使得<code>foo()</code>已经执行完毕,后其内部作用域依然存在,以供<code>bar()</code>在需要时访问使用。<code>bar</code>对<code>foo</code>的内部作用域的引用就是闭包。闭包使得函数可以访问自己在定义时的词法作用域。其实,当把函数类型的值进行传递,然后该函数在别处被调用时就会出现闭包。例如,下面的代码大家一定都熟悉:
function saySomething(msg){
setTimeout(function timer(){
console.log(msg);
},1000);
}
saySomething("+1s")
这里就有闭包,把<code>timer()</code>传递给<code>setTimeout(..)</code>,<code>timer()</code>就具有涵盖<code>saySomething(..)</code>内部作用域的闭包,实现对变量<code>msg</code>的引用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
当我还十分懵懂的去理解闭包的时候,书中给了一个经典的循环与闭包的问题:
for(var i = 1;i <= 5;i++){
setTimeout(function timer(){
console.log(i)},i*1000);
}
对于这段代码,我单纯地以为会每隔一秒打印出1,2,3,4,5。把这段代码运行一下看看:
脸有点疼。。。下面来分析一下why。打印出了一个5,然后每个一秒打印出一个6,打印出的5是<code>setTimeout</code>的id,与闭包无关,先不管。我们知道<code>setTimeout</code>是异步的,这就需要等待同步代码执行完成才会执行异步代码,这里的同步代码就是<code>for</code>循环啦。好的,先进行<code>for</code>循环,它会把当前<code>i</code>的值赋给<code>i*1000</code>里的<code>i</code>,但是<code>for</code>循环1不可能进到<code>timer</code>里面去给<code>i</code>赋值,这样把<code>setTimeout</code>压入队列时就相当于这样:
setTimeout(function timer(){console.log(i);},1*1000);
setTimeout(function timer(){console.log(i);},2*1000);
setTimeout(function timer(){console.log(i);},3*1000);
setTimeout(function timer(){console.log(i);},4*1000);
setTimeout(function timer(){console.log(i);},5*1000);
等循环完成时,<code>i</code>的值就是6了,队列里的异步代码开始执行就会打印出5个6了。当然,不管干啥,都要更深一点是吧,为什么这样呢?尽管上面的5个<code>setTimeout</code>是在各自的循环迭代中定义的,但是由于是闭包,都是引用了外一层的内部作用域,也就是说共享了同一个<code>i</code>。这个问题提醒我们在循环中要注意闭包的作用域。
既然要注意闭包的作用域,怎么才能让上面的例子每隔1秒出处1~5呢?知道原因解决起来也就方便了,一种方法是利用立即执行函数表达式(IIFA),改写如下:
for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)},j*1000)
})(i)
}
如果对ES6熟悉一点,也可以使用<code>let</code>:
for(let i = 1;i <= 5;i++){
setTimeout(function timer(){
console.log(i)},i*1000);
}
鉴于我也是个js方面的萌新,有什么不足之处还望大家指正。这次入门笔记就到这里吧,溜了溜了~