闭包:函数记住它的出生环境

定义:闭包 = 函数 + 它定义时所在的作用域。内层函数引用外层变量后,即使外层函数已经执行完,那些变量也继续存活——被内层函数「带走」了。

[迷你控制台]

一、状态工厂:每次调用生成独立的一份状态

function createCounter(label) {
  let count = 0              // 每调用一次 createCounter,就有一份新的 count
  return {
    next() { count += 1; return count },
    get()  { return count }
  }
}
const a = createCounter('a')
const b = createCounter('b')   // a 和 b 各自持有互不相干的 count

计数器 A

0

计数器 B

0

两个计数器的 count 是完全独立的私有变量:外部拿不到它们,只能通过返回的方法推动。这就是「模块封装」的最小版本。

二、私有变量:余额只能通过方法来动

function createAccount(initial) {
  let balance = initial                 // 闭包里的私有状态
  return {
    deposit(amount) { balance += amount; return balance },
    withdraw(amount) {
      if (amount > balance) return '余额不足'
      balance -= amount
      return balance
    }
  }
}
const acct = createAccount(100)
acct.balance        // undefined —— 从外面根本读不到
acct.deposit(50)    // 150
100

三、once:让函数只生效第一次

function once(fn) {
  let done = false            // 闭包记住「是否已经执行过」
  return function (...args) {
    if (done) return '(已失效,本次调用被忽略)'
    done = true
    return fn(...args)
  }
}
const claimCoupon = once(() => '首单立减 20 元,已领取')

同类套路还有 memoize(fn)(缓存计算结果,参数相同直接返回缓存)——差别只在闭包里存的是「执行状态」还是「结果缓存」。

四、经典陷阱:var 循环变量共享,let 每轮独立

for (var i = 0; i < 3; i++) {
  setTimeout(() => log('var 版本:' + i), 300)   // 3 3 3!三个回调共享同一个 i
}

for (let j = 0; j < 3; j++) {
  setTimeout(() => log('let 版本:' + j), 300)   // 0 1 2 —— 每轮 j 是独立绑定
}

原因:var 只有一份循环变量,回调执行时循环早已结束、i 停在 3;let 每次迭代创建一个新绑定,每个回调记住自己那一轮的 j。把 setTimeout 换成事件监听、数组回调,现象完全一样——循环里创建函数,就用 let。