MST

星途 面试题库

面试题:深入理解JavaScript作用域在异步操作中的表现

在JavaScript中,考虑如下代码片段:function asyncFunction() { let a = 1; setTimeout(() => { console.log(a); }, 1000); a = 2; } asyncFunction(); 请解释为什么在setTimeout的回调函数中打印出的是2而不是1。详细说明在这个异步场景下,作用域是如何工作的,以及它与同步代码中作用域机制的不同之处。
42.5万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试
  1. 为什么打印出的是2而不是1
    • 在JavaScript中,setTimeout 是异步操作。当 asyncFunction 执行时,let a = 1; 声明并初始化 a 为1,接着 a = 2; 这行代码将 a 的值更新为2 。
    • setTimeout 的回调函数会在1000毫秒(1秒)后放入任务队列中等待执行。此时,asyncFunction 函数的同步部分已经执行完毕,a 的值已经变为2 。当回调函数执行时,它访问的是当前作用域中的 a,此时 a 的值为2,所以打印出的是2 。
  2. 异步场景下作用域的工作原理
    • 在这个例子中,setTimeout 的回调函数处于 asyncFunction 的作用域内。JavaScript使用词法作用域(静态作用域),这意味着函数的作用域在定义时就确定了,而不是在调用时确定。
    • 回调函数虽然在1000毫秒后执行,但它依然可以访问 asyncFunction 作用域中的变量。由于 a 是用 let 声明的,它具有块级作用域,且在 asyncFunction 函数内有效。当回调函数执行时,它能访问到 asyncFunction 作用域中最新的 a 值,即2 。
  3. 与同步代码中作用域机制的不同之处
    • 同步代码
      • 在同步代码中,作用域按照代码的执行顺序依次创建和销毁。例如:
function syncFunction() {
    let b = 1;
    console.log(b);
    {
        let b = 2;
        console.log(b);
    }
    console.log(b);
}
syncFunction();
 - 这里在函数 `syncFunction` 中,首先声明 `b` 并赋值为1 ,打印1 。然后在块级作用域内重新声明 `b` 并赋值为2 ,打印2 。当块级作用域结束,内部声明的 `b` 销毁,最后再次打印外部的 `b` ,输出1 。作用域的变化与代码的同步执行紧密相关。
  • 异步代码
    • 异步代码(如 setTimeout 回调)打破了这种顺序执行的模式。异步操作的回调函数在将来某个时间执行,此时同步代码可能已经执行完毕,作用域中的变量状态可能已经发生了变化。但回调函数依然基于其定义时的词法作用域来访问变量,而不是基于其执行时的“瞬间”作用域状态。例如 setTimeout 回调函数虽然执行晚,但它依据 asyncFunction 定义时的作用域来访问 a 变量,所以能获取到更新后的 a 值2 。