一道 JavaScript 面试题涵盖 3 个主题

2025-05-25

一道 JavaScript 面试题涵盖 3 个主题

编程面试很难。面试时现场写代码更是难上加难。
我感觉,只要我得在别人面前敲代码myIntelligence -= 10;
,我现在的公司首席开发人员就会定期面试潜在的新候选人。公司为 JavaScript 开发人员准备了一些面试题,但几乎总是会问到这样一个问题:

    // what will be logged in the console
    // and how to fix it to log 0, 1, 2??
    for (var i = 0; i < 3; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000);
    }
Enter fullscreen mode Exit fullscreen mode

这是一个很典型的问题。
它有什么特别之处呢?
嗯,在我看来,这五行代码触及了 JavaScript 的三个有趣方面。

  • var、let 和 const
  • 闭包
  • 事件循环

让我们分解一下,看看这里发生了什么。

var let const

ES6 引入了新的变量赋值关键字:constlet。你可能已经知道它们是块级作用域的,并且var是函数级作用域的。
以下是一些简单的示例来说明这种行为。

    // simplest JavaScript example in the world:
    {
      var name = 'maciek';
      let surname = 'chmura';
    }
    console.log(name); // -> maciek
    console.log(surname); // -> surname is not defined

    // what's happening under the hood:
    var name;
    {
      let surname;
      name = 'maciek';
      surname = 'chmura';
    }
    console.log(name);
    console.log(surname);
Enter fullscreen mode Exit fullscreen mode

好的,让我们将其放在 for 循环的上下文中。

    for (var i = 0; i < 3; i++) {
      console.log(i); // -> 0 1 2
    }

    // what will happen when var is changed to let?
    for (let j = 0; j < 3; j++) {
      console.log(j); // -> 0 1 2
    }
Enter fullscreen mode Exit fullscreen mode

两个循环都生成正确的输出。但方式略有不同。var“跳转”到循环,global scope并“跳入”循环,并在每次迭代时进行初始化。 可以这样说明:let

    // var lives here
    for (var i = 0; i < 3; i++) {
      console.log(i); // -> 0 1 2
    }
    console.log(i); // -> 3

    for (let j = 0; j < 3; j++) {
      // let is available only from here
      console.log(j); // -> 0 1 2
    }
    console.log(j); // ReferenceError: j is not defined
Enter fullscreen mode Exit fullscreen mode

好的,非常简单...这就是块作用域的工作方式...继续。

闭包

JavaScript 闭包的神秘世界。
闭包的原始定义是什么?
可以去MDN查看。

闭包是函数和声明该函数的词法环境的组合。

请深入阅读 MDN 的这篇文章。这个知识库的贡献者非常聪明,让我们相信他们 :)

  • 这到底是什么lexical environment
  • 它会在某个时候消失吗?
  • 谁、何时决定?
  • 我该如何控制它?

很长一段时间我都无法理解。
直到我添加了两个视觉辅助工具来帮助我理解。

  1. 🎒 背包。我喜欢把闭包想象成函数的背包。当一个函数被定义时,它会把所有未来可能需要的值都添加到背包里。
  2. 🚚 垃圾收集器。一辆清理旧代码的卡车。与 C 语言不同,你无需执行任何操作malloc()free()它会被自动处理。

当某个函数执行完毕并返回一个值时,我们可以安全地从内存中移除此函数定义🚚🗑。对于不再可访问的值,也是如此。
当函数返回一个函数时,事情会变得有趣。
我不想重新发明新的例子和定义,所以我只会添加一些可视化的帮助。MDN
示例(带行号):

    function makeFunc() {          // 1
      var name = 'Mozilla';        // 2
      function displayName() {     // 3
        alert(name);               // 4
      }                            // 5
      return displayName;          // 6
    }                              // 7
                                   // 8
    var myFunc = makeFunc();       // 9
    myFunc();                      // 10
Enter fullscreen mode Exit fullscreen mode

让我们想象一个简化的 JavaScript 解释器工作流程。JavaScript 运行时在运行代码时在“思考”什么。

  • (第 1 行)makeFunc函数定义,继续。
  • (9)声明myFunc变量并将运行结果赋给它makeFunc,执行makeFunc
  • (1)跳入makeFunc定义。
  • (2)好的,一个name值为的变量Mozilla
  • (3)displayName函数定义,继续。
  • (4)return displayName函数定义

第一个情节转折。这里返回了完整的函数定义。 的末尾没有 () displayName
第二个情节转折。观察到一个闭包。Where?displayName将其放入其 🎒 中var name(它在 的词法范围内displayName)。

屏幕

makeFunc执行并返回了 的整个函数定义displayName及其闭包(a 🎒),该闭包保存了对 中值的引用name
垃圾收集器无法从内存中删除第 1 行到第 7 行,因为将来某个时候myFunc可能会执行这些代码,到时候displayName就需要用到它们的闭包了。

  • (10)执行myFunc

这就是我理解的闭包。
现在我明白了!

熊猫

让我们进入谜题的最后一部分。

事件循环

学习事件循环的最佳方式莫过于 Philip Roberts 在 JSConf EU 上的精彩演讲。
快来观看吧……


🤯 是不是有点不可思议?
好!最后,了解了所有知识之后,我们来分析一下这道面试题到底讲了什么。

    for (var i = 0; i < 3; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000);
    }
Enter fullscreen mode Exit fullscreen mode

随着循环的每次迭代,setTimeout将 发送console.log(i)到 Web API 的函数并开始倒计时。
与此同时,我们将继续循环。另一个console.log(i)将被推送到 Web API,依此类推......
循环完成执行。调用堆栈为空。
在 Web API 中,1 秒后console.log(i)被推送到回调队列。然后又一个,又一个。
由于调用堆栈为空,回调队列可以将其第一个元素推送到调用堆栈以执行它。
因此第一个console.log(i)执行。
它查找i
的值是多少i
它是 3。从全局范围来看。
为什么? 循环
完成迭代并i在最后将 更新为 3。var是函数作用域(for 循环不是函数),并且被提升到循环之外 调用堆栈再次为空。 第二个移至调用堆栈。 的值是多少?它又是 3。它是相同的值。
iglobal scope

console.log(i)
i

得到它

如何修复它以记录 0、1、2?
一种修复方法是将其更改varlet
现在,在循环中,每个 都i被初始化并赋值给当前迭代的值,然后放入将记录它的函数的闭包(一个🎒)中。1
秒后,当调用堆栈为空时,回调队列会将带有console.log(i)及其闭包值的函数i推回调用堆栈并执行它。0、1、2
将分别被记录。
完成。

请问下一个问题。

现在,当您知道到底发生了什么事情时,还能做些什么来解决它呢?

免责声明:
我写这篇文章主要是为了自己研究这些主题。如果有什么错误,请在评论中指出,以便我们大家共同学习 :)

文章来源:https://dev.to/maciekchmura/3-topics-in-1-javascript-interview-question-1gd7
PREV
我如何构建 React 项目
NEXT
我创建了一个终端风格的网站。