变量提升与作用域面试题总结

353 阅读3分钟

JavaScript 变量提升与作用域面试题总结

1. 变量提升基础题

题目

// 面试题:分析以下代码的执行结果
console.log(a); // ?
console.log(b); // ?
console.log(c); // ?

var a = 1;
let b = 2;
const c = 3;

function test() {
  console.log(a); // ?
  var a = 4;
  console.log(a); // ?
}
test();

答案

  • console.log(a) → 输出:undefinedvar a 被提升为声明,值为 undefined
  • console.log(b) → 抛出:ReferenceError: Cannot access 'b' before initializationlet b 处于暂时性死区)
  • 由于在第二行抛错,后续的 console.log(c)test() 都不会执行

如果单独看函数:

  • console.log(a)(函数内第一行)→ undefined(函数内 var a 提升,遮蔽外层的 a
  • console.log(a)(函数内第二行)→ 4

考点

  • 变量提升(Hoisting)var 声明提升到作用域顶部,初始值为 undefined
  • 暂时性死区(TDZ)let/const 存在提升但不可访问,在实际声明前访问会抛 ReferenceError
  • 作用域与遮蔽:函数内 var a 遮蔽外层同名变量
  • 执行中断:一旦抛出未捕获异常,后续语句不再执行

2. 经典循环闭包题

题目

// 面试题:以下代码输出什么?如何修复?
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}

答案

输出三次 3

原因var 是函数作用域,三个回调共享同一个 i,循环结束 i 为 3

修复方案

方案1:使用 let(推荐)
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 0, 1, 2
  }, 100);
}
方案2:使用闭包(IIFE)
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => {
      console.log(j); // 0, 1, 2
    }, 100);
  })(i);
}
方案3:使用 bind
for (var i = 0; i < 3; i++) {
  setTimeout(console.log.bind(null, i), 100); // 0, 1, 2
}

核心原理

  • var 版本:整个循环只有一个 i 变量,所有回调共享同一引用
  • let 版本:每次迭代创建新的块级绑定,每个回调捕获不同的 i
  • 闭包方案:通过参数传递,把当前 i 的值作为"快照"传给内层函数

3. var vs let 对比总结

特性varlet
作用域函数作用域(或全局)块级作用域
变量提升提升声明,初始值 undefined提升但处于 TDZ,声明前访问报错
重复声明允许同一作用域内不允许
全局对象属性会成为全局对象属性不会
for 循环行为所有迭代共享同一变量每次迭代创建新绑定

示例对比

// 作用域差异
function test() {
  if (true) {
    var a = 1;
    let b = 2;
  }
  console.log(a); // 1(var 可访问)
  console.log(b); // ReferenceError(let 块外不可访问)
}

// 提升差异
console.log(a); // undefined
console.log(b); // ReferenceError
var a = 1;
let b = 2;

4. 常见陷阱与最佳实践

陷阱1:意外的全局变量

function test() {
  a = 1; // 没有声明关键字,成为全局变量!
}

陷阱2:块级作用域误用

if (true) {
  var a = 1; // 实际是函数作用域
}
console.log(a); // 1(可能不是预期行为)

最佳实践

  1. 优先使用 const,需要重新赋值时使用 let
  2. 避免使用 var,除非需要兼容老版本浏览器
  3. 在声明前不要使用变量,避免依赖提升行为
  4. 使用严格模式'use strict')防止意外全局变量

5. 面试要点总结

必须掌握的概念

  • 变量提升:提升到当前作用域顶部,不是文件顶部
  • 暂时性死区let/const 的特殊行为
  • 作用域链:变量查找规则
  • 闭包原理:函数记住其词法作用域

回答技巧

  1. 先说结果,再解释原因
  2. 提到关键概念:提升、作用域、TDZ 等
  3. 给出修复方案,展示解决问题的能力
  4. 举一反三,说明在实际开发中的应用