面试题库 词条目录
前端Wiki/面试题库/JavaScript/JS 基础题

作用域与闭包

作用域是变量的可见范围。JS 历经三个阶段:全局作用域 / 函数作用域(var 时代)→ 块级作用域(let / const 时代)。var 声明会提升到作用域顶部且值为 undefined、可重复声明;let / const 也有提升但处于“暂时性死区(TDZ)”,声明前访问直接抛 ReferenceError。

闭包是“函数 + 它创建时所在的词法作用域”的组合:内层函数引用了外层变量,外层函数 return 后这些变量依然存活。用途:模块化(IIFE 封装私有变量)、函数柯里化、缓存。经典陷阱:for + var + setTimeout 打印全是终值,因为循环里所有回调共享同一个 i;改 let 或 IIFE 传参即可。

  • var 提升可重复声明,let / const 有暂时性死区、块级作用域
  • 闭包 = 函数 + 创建时的词法作用域;外层返回后内部变量仍存活
  • for + setTimeout 经典题:var 全打印终值,改 let 或用 IIFE 传参解决
JS
// 1. 变量提升与暂时性死区
console.log(a);           // undefined(var 提升)
var a = 1;
try { console.log(b); } catch (e) { console.log('TDZ:', e.message); }  // ReferenceError
let b = 2;

// 2. 闭包:外层返回后 count 依然存活(模块模式)
function counter() {
  let count = 0;                       // 私有变量:外部拿不到,只能靠闭包操作
  return {
    inc: () => ++count,
    get: () => count,
  };
}
const c = counter();
c.inc(); c.inc();
console.log('count =', c.get());       // 2

// 3. 经典面试题:var 打印 3 个 3,let 打印 0 1 2
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 0);       // 3 3 3(共享同一个 i)
}
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let:', j), 0);       // 0 1 2(每轮独立作用域)
}

// 4. IIFE 立即执行函数:最古老的模块化与闭包应用
const secret = (() => {
  const key = 'hidden';
  return { getKey: () => key };
})();
console.log(secret.getKey());          // hidden
本节要点闭包的本质是词法作用域的延续:函数带走它出生时的变量;var / let 的区别就在提升与块级作用域。