一、闭包的概念
1、多种概念多种理解
当函数可以记住并访问所在的词法作用域的时候,就产生了闭包,即使函数是在当前词法作用域之外执行
闭包就是能够读取其他函数内部变量的函数,可理解成“定义在一个函数内部的函数”
闭包让你可以在一个内层函数中访问到其外层函数的作用域
来看这段代码:
function foo() {
var a = 2
function bar() {
console.log(a);
}
bar()
}
foo()
上面这段代码,从技术上讲,也许是闭包,但是从上面的定义上来讲并不是闭包,上面最准确地用来解释bar()对a的引用的方法是词法作用域的规则,而这些规则只是闭包的一部分(但却是非常重要的一部分)
而下面的这段代码清晰的展示了闭包:
function foo() {
var a = 2
function bar() {
console.log(a);
}
return bar //将bar()函数本身当作一个值类型进行传递
}
var baz = foo()
baz()//2
不光可以通过return的形式,还可以通过函数传参的形式:
function foo() {
var a = 2
function baz() {
console.log(a);
}
bar(baz) //将baz()当成了一个函数的参数在外调用
}
function bar(fn) {
fn()
}
foo()
二、深入理解
只要使用了回调,就是使用了闭包
1、关于for循环
function foo() {
for(var i = 1; i <= 3; i++) {
setTimeout(function timer() { //这里使用了一个回调,形成了一个闭包
console.log(i);
},i*1000)
}
}
foo()//会一秒一次的输出3个4
为何不是我们预期的一秒一次地分别输出1,2,3?
当定时器运行的时候即使每个迭代中执行的是setTimerout(…,0),所有的回调函数依然是在循环结束后才会被执行,因此每次会输出一个4,
我们认为每次的1,2,3的输出,是我们认为循环中每个迭代在运行的时候都会自己捕获一个 i 的副本,但是根据作用域的原理,实际上是
尽管循环中的三个函数是在每次迭代中分别定义的,但是它们都被封闭到一个共享的全局作用域,因此实际上只有一个 i
将上面的代码进行原理解析:
function foo() {
var i =1
setTimeout(function timer() {
console.log(i);
},1000)
var i =2
setTimeout(function timer() {
console.log(i);
},2000)
var i =3
setTimeout(function timer() {
console.log(i);
},3000)
}
foo()
//我们知道计时器setTimeout是异步里面的宏任务,是在主线程执行完之后,再执行的,所以我们再分析可以将上面的代码解析成:
function foo() {
var i =1
var i =2
var i =3
setTimeout(function timer() {
console.log(i);
},1000)
setTimeout(function timer() {
console.log(i);
},2000)
setTimeout(function timer() {
console.log(i);
},3000)
}
foo()
2、我们如何使用闭包来解决这个问题?
解决思路:我们需要更多的闭包作用域,特别是在循环过程中的每个迭代都需要一个闭包作用域
(1)在迭代里使用立即执行函数 来为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值变量来供我们访问
function foo() {
for(var i = 1; i<= 5; i++) {
(function(j) { //迭代中每次立即执行函数的执行都会创建一个新的函数作用域
setTimeout(function timer() {
console.log(j);
}, j*1000)
})(i)
}
}
foo()
(2) 更简单的方法
function foo() {
for(let i = 1; i <= 3; i++) {//let 将变量绑定到其所在的定义域
setTimeout(function timer() {
console.log(i);
},i*1000)
}
}
foo()