likes
comments
collection
share

JavaScript 面试糗事:闭包问题中的迷失循环变量

作者站长头像
站长
· 阅读数 29

前言

在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之前,我们先肉眼运行一下代码。分析一下题目的原代码。

  1. 创建一个arr数组
  2. 通过第一个for循环循环10次,每次循环往数组里添加一个函数体。
  3. 通过第二个for循环循环10次,遍历调用数组中的函数体。

代码的输出结果是10个10。

JavaScript 面试糗事:闭包问题中的迷失循环变量

当使用 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声明的变量中,每次迭代都会创建一个新的变量,因此闭包中捕获的是每次迭代的当前值。

JavaScript 面试糗事:闭包问题中的迷失循环变量

这样的块级作用域会有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 可以确保在循环中每次迭代时都创建一个新的变量,从而解决了闭包中捕获到的值不正确的问题。这样,闭包中就能够访问到正确的循环迭代值,而不是循环结束时的值。

修改后结果为:

JavaScript 面试糗事:闭包问题中的迷失循环变量

方法二: 把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 值。

修改代码执行效果:

JavaScript 面试糗事:闭包问题中的迷失循环变量

小结

解决闭包中捕获循环变量值不正确的两种方案:

  • 一是利用 let 关键字创建块级作用域,确保变量在每次迭代时都是新的;

  • 二是利用立即执行函数创建闭包,确保闭包中捕获的是当前循环迭代的值。

闭包是面试的重点,掌握好闭包是成功上岸的基础。

转载自:https://juejin.cn/post/7366148671988449317
评论
请登录