MDN 对闭包的定义为:bash
闭包是指那些可以访问自由变量的函数。闭包
什么又是自由变量呢?异步
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。函数
举个例子:post
var a = 0; //自由变量
function foo() {
console.log(a);//访问自由变量,此时这个变量并非函数参数或者函数的局部变量
}
foo();
复制代码
foo 函数能够访问变量 a,可是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,因此咱们说 a 就是自由变量,那么函数 foo 就造成了一个闭包。ui
因此在《 JavaScript权威指南 》中讲到:从技术的角度讲,全部的 JavaScript 函数都是闭包。spa
在ECMAScript中,闭包指的是:线程
接下来就来说讲实践上的闭包。code
如下代码为何与预想的输出不符?队列
// 代码1
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 输出5次5
}, 0)
}
复制代码
假设A:由于 setTimeout
这块的任务直接进入了事件队列中,因此 i
循环以后i先变成了5,再执行 setTimeout
, setTimeout
中的箭头函数会保存对i的引用,因此会打印5个5.
// 代码2
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i) // 输出 0,1,2,3,4
}, 0)
}
复制代码
假设结论 A 成立,那么上式应该也是输出5次5,可是很明显不是,因此结论A并不彻底正确。
那咱们去掉循环,先写成最简单的异步代码:
function test(a){
setTimeout(function timer(){
console.log(a)
},0)
}
test('hello')
复制代码
复制代码执行 test
,setTimeout
将 timer
函数放入了事件队列,timer
保留着 test
函数的做用域(在函数定义时建立的),test
执行完毕,主线程上没有其余任务了,timer
从事件队列中出队,执行 timer
,执行 console.log ( a )
,因为闭包的缘由,a 依然会保留着以前的引用,输出 'hello'
。
那咱们在回到题目中,由于两段代码中的不一样只有声明语句,因此咱们提出假设B
:由于在代码1
中,匿名函数保留着外部词法做用域,i
都是在全局做用域上,代码2
中因为存在块做用域,因此它保留着每次循环时i的引用。
// 代码3
for (var i = 0; i < 5; i++) {
((i) => {
setTimeout(function timer() {
console.log(i) // 输出 0,1,2,3,4
}, 0)
})(i)
}
复制代码
复制代码使用 IIFE
传递了变量i给匿名函数,IIFE
产生了一个新做用域,timer
中保留对匿名函数中的i的引用,因此会依次输出。
// 代码4
for (var i = 0; i < 5; i++) {
(() => {
setTimeout(function timer() {
console.log(i) // 输出 5个5
}, 0)
})()
}
复制代码
和代码3
的区别为IIFE
没有给匿名函数传递 i,timer
保留的做用域链中对i的引用仍是在全局做用域上。
通过以上两个变体的验证,因此假设B
成立,即:因为做用域链的变化,闭包中保留的参数引用也发生了变化,输出的参数也发生了变化。
下例,循环中的每一个迭代器在运行时都会给本身捕获一个i
的副本,可是根据做用域的工做原理,尽管循环中的五个函数分别是在各个迭代器中分别定义的,可是它们都会被封闭在一个共享的全局做用域中,实际上只有一个i
,换句话说,i
的值在传入内部函数以前,已经为 6 了,因此结果每次都会输出 6 。
for(var i=1; i <= 5; i++){
setTimeout(function(){
console.log(i);//6
},0)
}
复制代码
解决上面的问题,在每一个循环迭代中都须要一个闭包做用域,下面示例,循环中的每一个迭代器都会生成一个新的做用域。
for(var i=1; i <= 5; i++){
(function(j){
setTimeout(function(){
console.log(j);
})
},0)(i)
}
复制代码
也可使用let
解决,let
声明,能够用来劫持块做用域,而且在这个块做用域中生明一个变量。
for(let i=1; i <= 5; i++){
setTimeout(function(){
console.log(i);
},0)
}
复制代码
简单的说:函数 + 自由变量就造成了闭包。其实并非特别复杂,只是咱们须要在引用自由变量的时候当心做用域的变化。
新手写做,若是有错误或者不严谨的地方,请大伙给予指正。若是这片文章对你有所帮助或者有所启发,还请给一个赞,鼓励一下做者,在此谢过。