面试官:输出结果是什么?为什么?怎样改?还有呢?

328 阅读3分钟

面试时,面试官给了你一段代码:

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。殊不知,这是面试官给你挖的坑,结果你还高兴的跳了进去,凉凉。

而有些人很警觉,看着不对劲,仔细思考了下,认真回答:10个‘10’,面试官点点头,你心里一松,顿时感到舒畅,自信心起来了,还没缓口气,面试官接着提问,为什么?这个时候你懵了,你心里知道,但是不知道怎么表达,然后说了堆乱七八糟的东西,结果没get到点,回答完后,气氛尴尬起来了,你恨不得找个洞钻进去,因为你也不知道你刚才说的是什么东西。

不过没关系,看完这篇文章后,以后遇到这种情况就不会陷入尴尬的场景了。

for循环无法形成作用域,var 声明的变量 i 在整个函数作用域内都是可见的,包括循环和循环外的部分。因此,当第一个for循环结束后,i 的值变为 10,而函数只是声明赋值了,并没调用,也就是说还没有执行,并且所有的函数共享这个 i 变量。当执行第二个for循环时才执行这些函数,此时全局作用域中的 i 已经是为 10,因此每个函数都输出 10。

因为你看过这篇文章,所以你轻松流畅的解释完,但你以为就到此结束了吗?

面试官问:怎样改才能输出0到9?

还好,你很快的说出把var改为let,并顺便把原因说了:

var 声明的变量具有函数作用域,而不是块作用域。这意味着在使用 var 声明的变量在整个函数范围内都是可见的,而不仅仅是在声明它们的块内部。在原始的代码中,for 循环中的 i 变量实际上是在外部函数作用域中声明的,因此对所有的循环迭代都是可见的。

在循环的每一次迭代中,i 的值都会递增,最终达到 10。当循环结束后,所有通过闭包引用的函数都会共享相同的 i 变量,这个变量的值已经是循环结束时的最终值 10。因此,无论调用哪个函数,它们都会输出 i 的最终值,即 10。

而使用 let 声明的变量具有块作用域,每次迭代会创建一个新的 i 变量,因此每个闭包捕获的是不同的 i 值,而不是共享同一个 i 变量。

你简直是个天才,回答的太完美了,但此时面试官还不愿意放过你,问:还有其他方法吗?

此时,你有点汗流浃背了,CPU快速运转,灵机一动,你想到了闭包,于是你这样说:

可以使用闭包来创建函数的副本,以便在每次迭代中捕获 i 的当前值。例如,可以使用立即执行函数表达式(IIFE)来创建闭包,如下所示:

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]();
}

在这个修正后的版本中,每个函数都有自己的闭包,捕获了在循环中当前迭代的 i 的值。

面试官听完后,说了句话:“可以。”

你终于松了口气,再也不敢小看任何一道提问。

你感叹道:小小的一段代码,包含了作用域,作用域链,闭包等难点,果然不可小觑啊!

如果你不了解上述知识点,可以先看这两篇文章:

juejin.cn/post/736442…

juejin.cn/post/736581…

加深理解!谢谢。