这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
一、闭包的定义
MDN 对闭包的定义为:
闭包是指那些能够访问自由变量的函数。
那什么是自由变量呢?
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
由此,我们可以看出闭包共有两部分组成:
闭包 = 函数 + 函数能够访问的自由变量
简单来讲:
闭包就是能够读取其他函数内部变量的函数
在函数 A 中还有函数 B,函数 B 调用了函数 A 中的变量,那么函数 B 就称为函数 A 的闭包。
function foo() {
let a = 2;
function bar() {
console.log(a);
}
bar();
}
foo(); // 2
二、循环和闭包
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log("i: ", i);
}, 1000);
}
console.log(i);
// 输出
// 5;
// i: 5;
// i: 5;
// i: 5;
// i: 5;
// i: 5;
要理解上述代码,首先我们得了解一下概念:
- JS 分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
上述代码中使用定时器,当 JS 引擎线程执行到该段代码,便把定时器放到定时器线程去计时,此时 JS 引擎线程执行同步栈里面的任务。当定时器计时完成之后,将回调函数推入消息队列。等待栈中的代码执行完毕之后会去读取消息队列中的事件。
由于 JS 的函数作用域,当回调函数被推入消息队列的时候没有带上参数。for 循环结束之后,因为 i 是用 var 定义的,所以 var 是全局变量(这里没有函数,如果有就是函数内部的变量),这个时候的 i 是 5。
如何解决?
// let 是块级作用域,当前块是循环体
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log('i: ',i);
}, 1000);
}
console.log(i);
// 输出
// 5
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
//这是函数级作用域,利用闭包模拟块级作用域
for (var i = 0; i < 5; i++) {
(function(){
var j = i;
setTimeout(function() {
console.log('i: ',j);
}, 1000);
})();
}
console.log(i);
// 输出
// 5
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
// 或者采取传参的方式,使用了闭包
for (var i = 0; i < 5; i++) {
(function(j){
setTimeout(function() {
console.log('i: ',j);
}, 1000);
})(i);
}
console.log(i);
// 输出
// i is not defined
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4
// setTimeout 的第三个参数
for (var i = 0; i < 5; i++) {
setTimeout(function timer(j) {
console.log('i: ', j);
}, 1000, i);
}
console.log(i);
// 输出
// i is not defined
// i: 0
// i: 1
// i: 2
// i: 3
// i: 4