3 min read

你不知道的JavaScript:作用域与闭包

Table of Contents

简介

《Scope & Closures》深入讲解 JavaScript 的作用域机制和闭包,这是 JS 核心概念中的核心。


编译原理

JavaScript 引擎执行代码前会经过编译阶段:

  1. 词法分析 (Lexing):将代码分解为 token
  2. 解析 (Parsing):生成 AST(抽象语法树)
  3. 代码生成:生成可执行代码

理解 LHS 和 RHS 查询

function foo(a) {
  console.log(a + b); // b 是 RHS 查询(需要读取值)
  b = a;              // b 是 LHS 查询(需要赋值)
}

foo(2);
  • LHS (Left-Hand Side):找到变量的容器(赋值操作)
  • RHS (Right-Hand Side):找到变量的值(读取操作)

作用域链

function outer() {
  let a = 1;
  
  function middle() {
    let b = 2;
    
    function inner() {
      let c = 3;
      console.log(a + b + c); // 层层向外查找
    }
    
    inner();
  }
  
  middle();
}

outer(); // 输出 6

作用域查找顺序:内层 → 外层 → 全局


闭包

什么是闭包?

闭包是指一个函数能够”记住”并访问其定义时所在作用域的变量,即使该函数在定义时的作用域之外执行。

function createCounter() {
  let count = 0;
  
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

经典案例:定时器

for (var i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出: 4, 4, 4 (var 是函数作用域,循环结束后 i = 4)

// 解决方式 1: 使用 let
for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出: 1, 2, 3 (let 是块级作用域,每次循环创建新变量)

// 解决方式 2: 闭包
for (var i = 1; i <= 3; i++) {
  ((j) => setTimeout(() => console.log(j), 100))(i);
}

经典案例:模块模式

const Module = (function() {
  let _count = 0;
  
  return {
    getCount: () => _count,
    increment: () => _count++,
    reset: () => _count = 0
  };
})();

Module.getCount();   // 0
Module.increment();
Module.getCount();   // 1

变量提升 (Hoisting)

var 提升

console.log(a); // undefined (不是 ReferenceError!)
var a = 2;

// 相当于:
var a;           // 声明提升到顶部
console.log(a);  // undefined
a = 2;           // 赋值留在原位

let 和 const 提升(暂时性死区)

// console.log(a); // ReferenceError!
let a = 2;

// let 也有提升,但提升后到声明之间的区域叫"暂时性死区"(TDZ)

函数提升

foo(); // "foo"
function foo() {
  console.log("foo");
}

// 函数表达式不会提升
// bar(); // ReferenceError!
var bar = function() {
  console.log("bar");
};

块级作用域

let

{
  let blockVar = "我在块里";
}
// console.log(blockVar); // ReferenceError

const

const PI = 3.14159;
// PI = 3; // TypeError: 赋值常量

// const 对象可以修改属性
const obj = { name: "Lily" };
obj.name = "Rose"; // OK
// obj = {}; // TypeError: 不能重新赋值

小结

  • ✅ 理解作用域链的工作原理
  • ✅ 掌握闭包的应用场景
  • ✅ 理解变量提升机制
  • ✅ 优先使用 letconst
  • ✅ 警惕闭包带来的内存泄漏问题

相关阅读: