JavaScript 面试糗事:闭包问题中的迷失循环变量
前言
在JavaScript 编程中,闭包是一个既重要又常令人困惑的概念。闭包作为 JavaScript 的核心特性之一,在面试中占据着重要地位。掌握闭包是成功上岸的前提。
正文
经典面试题
题目
通过修改下方代码得到输出结果为0,1,2,3,4,5,6,7,8,9。
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
for (var j = 0; j < arr.length; j++) {
arr[j]();
}
分析题目
在实现输出结果为0,1,2,3,4,5,6,7,8,9之前,我们先肉眼运行一下代码。分析一下题目的原代码。
- 创建一个arr数组
- 通过第一个for循环循环10次,每次循环往数组里添加一个函数体。
- 通过第二个for循环循环10次,遍历调用数组中的函数体。
代码的输出结果是10个10。
当使用 var 声明变量时,它是在函数作用域内声明的,而不是块级作用域。这意味着,在使用 var 声明的变量在整个函数体内都是可见的。在循环中使用 var 声明的变量,其作用域是整个循环体,而不是每次迭代都创建一个新的变量。
这就是为什么在原始代码中使用 var i
,在循环结束后,i的值为 10,因为闭包中捕获的是循环结束时的i值。
简单来说:for (var i = 0; i < 10; i++) {}
不能生成作用域,所以其作用域是全局作用域。在第二次循环输出i之前,全局的变量i的值已经通过第一次for循环增加到了值为10。在第二次循环输出i时,会在全局作用域内寻找i,此时i=10,所以输出了10个10。
为了要输出0到9,就不能让i成为全局变量。
题解
方案一:把i变成块级作用域内变量
修改后的代码:
var arr = []
for (let i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
}
}
for (var j = 0; j < arr.length; j++) {
arr[j]();
}
为了实现输出0到9,可以使用 let关键字声明变量。let 关键字会创建一个块级作用域的变量,而不是函数作用域的变量。在使用 let声明的变量中,每次迭代都会创建一个新的变量,因此闭包中捕获的是每次迭代的当前值。
这样的块级作用域会有10个。
for (let i = 0; i < 10; i++) {
// 第一次迭代
// i = 0
arr[i] = function () {
console.log(i);
};
// 第二次迭代
// i = 1
arr[i] = function () {
console.log(i);
};
//... 以此类推,直到第十次迭代
// 第十次迭代
// i = 9
arr[i] = function () {
console.log(i);
};
}
因此,使用let 替代var 可以确保在循环中每次迭代时都创建一个新的变量,从而解决了闭包中捕获到的值不正确的问题。这样,闭包中就能够访问到正确的循环迭代值,而不是循环结束时的值。
修改后结果为:
方法二: 把i变成函数作用域内变量
小tips:立即执行函数。
立即执行函数是一种在定义后立即被执行的函数表达式。
(function() {
// 函数内部的代码
})();
修改后的代码:
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = (function (num) {
return function () {
console.log(num);
};
})(i);
}
for (var j = 0; j < arr.length; j++) {
arr[j]();
}
为了实现输出0到9,可以使用立即执行函数来创建闭包。
上述代码中的立即执行函数接受一个参数num,并返回一个函数。在每次循环中,我们将当前的i值传递给这个立即执行函数。由于立即执行函数会立即执行,因此它会在每次循环迭代时都被调用,同时创建一个新的作用域,并将当前的i值传递给该作用域。这样,每个闭包都会捕获不同的i值。
在立即执行函数中,我们返回了一个新的函数。这个函数内部引用了传递进来的 num参数,而 num的值是在当前循环迭代中捕获的i 的值。因此,每个闭包函数都有自己独立的 num值,而不是共享同一个i值。
将返回的闭包函数赋值给数组 arr 的第i个位置。这样,数组arr 中的每个元素都是一个闭包函数,每个闭包函数都捕获了不同的i 值。
修改代码执行效果:
小结
解决闭包中捕获循环变量值不正确的两种方案:
-
一是利用 let 关键字创建块级作用域,确保变量在每次迭代时都是新的;
-
二是利用立即执行函数创建闭包,确保闭包中捕获的是当前循环迭代的值。
闭包是面试的重点,掌握好闭包是成功上岸的基础。
转载自:https://juejin.cn/post/7366148671988449317