定义:闭包 = 函数 + 它定义时所在的作用域。内层函数引用外层变量后,即使外层函数已经执行完,那些变量也继续存活——被内层函数「带走」了。
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
两个计数器的 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
function once(fn) {
let done = false // 闭包记住「是否已经执行过」
return function (...args) {
if (done) return '(已失效,本次调用被忽略)'
done = true
return fn(...args)
}
}
const claimCoupon = once(() => '首单立减 20 元,已领取')
同类套路还有 memoize(fn)(缓存计算结果,参数相同直接返回缓存)——差别只在闭包里存的是「执行状态」还是「结果缓存」。
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。