JavaScript闭包与作用域
约 1695 字大约 6 分钟
javascriptclosure
2025-07-23
概述
闭包是 JavaScript 中最重要也最容易被误解的概念之一。闭包使函数能够"记住"并访问其创建时的词法作用域,即使该函数在其词法作用域之外执行。闭包是模块模式、柯里化、高阶函数等众多编程范式的基础。
1. 词法作用域(Lexical Scope)
JavaScript 使用词法作用域(也称静态作用域),函数的作用域在定义时确定,而非调用时。
const globalVar = 'global';
function outer() {
const outerVar = 'outer';
function inner() {
const innerVar = 'inner';
console.log(innerVar); // 'inner' — 自身作用域
console.log(outerVar); // 'outer' — 外层作用域
console.log(globalVar); // 'global' — 全局作用域
}
inner();
}
outer();词法作用域 vs 动态作用域
const x = 10;
function foo() {
console.log(x); // 词法作用域:始终输出 10
}
function bar() {
const x = 20;
foo(); // 如果是动态作用域,这里会输出 20
}
bar(); // 输出 10 — JavaScript 是词法作用域2. 闭包的定义与形成
闭包 = 函数 + 其引用的外部词法环境。
function createCounter() {
let count = 0; // 被闭包捕获的变量
return {
increment() { return ++count; },
decrement() { return --count; },
getCount() { return count; }
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// count 变量在 createCounter 执行完毕后仍然存活
// 因为返回的方法通过闭包引用了它3. 闭包的核心应用场景
数据私有化(Data Privacy)
function createBankAccount(initialBalance) {
let balance = initialBalance; // 私有变量,外部无法直接访问
const transactions = [];
return {
deposit(amount) {
if (amount <= 0) throw new Error('Amount must be positive');
balance += amount;
transactions.push({ type: 'deposit', amount, date: new Date() });
return balance;
},
withdraw(amount) {
if (amount > balance) throw new Error('Insufficient funds');
balance -= amount;
transactions.push({ type: 'withdraw', amount, date: new Date() });
return balance;
},
getBalance() {
return balance;
},
getStatement() {
return [...transactions]; // 返回副本,防止外部修改
}
};
}
const account = createBankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 1300
// account.balance → undefined(无法直接访问)偏函数与柯里化(Partial Application & Currying)
// 偏函数
function multiply(a, b) {
return a * b;
}
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
const double = partial(multiply, 2);
const triple = partial(multiply, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, [...args, ...moreArgs]);
};
};
}
const curriedAdd = curry((a, b, c) => a + b + c);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6工厂函数
function createLogger(prefix) {
const startTime = Date.now();
return {
log(message) {
const elapsed = Date.now() - startTime;
console.log(`[${prefix}] +${elapsed}ms: ${message}`);
},
warn(message) {
const elapsed = Date.now() - startTime;
console.warn(`[${prefix}] +${elapsed}ms: WARNING - ${message}`);
}
};
}
const dbLogger = createLogger('DB');
const apiLogger = createLogger('API');
dbLogger.log('Connected'); // [DB] +5ms: Connected
apiLogger.log('Request received'); // [API] +8ms: Request received函数缓存(Memoization)
function memoize(fn) {
const cache = new Map(); // 闭包中的缓存
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveFib = (n) => {
if (n <= 1) return n;
return expensiveFib(n - 1) + expensiveFib(n - 2);
};
const fastFib = memoize(function fib(n) {
if (n <= 1) return n;
return fastFib(n - 1) + fastFib(n - 2);
});
console.log(fastFib(40)); // 瞬间返回 1023341554. 模块模式(Module Pattern)
闭包是 JavaScript 模块化的原始实现方式。
const UserModule = (function() {
// 私有状态
const users = [];
let nextId = 1;
// 私有函数
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// 公开 API
return {
addUser(name, email) {
if (!validateEmail(email)) {
throw new Error('Invalid email');
}
const user = { id: nextId++, name, email };
users.push(user);
return user;
},
getUser(id) {
return users.find(u => u.id === id);
},
getUserCount() {
return users.length;
}
};
})();
UserModule.addUser('Alice', 'alice@example.com');
console.log(UserModule.getUserCount()); // 1
// UserModule.users → undefined(私有)5. 经典陷阱:循环中的闭包
// 经典问题:var + 循环
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 全部输出 5
}, 100);
}
// 所有回调共享同一个 i,循环结束后 i = 5
// 解决方案 1: IIFE
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2, 3, 4
}, 100);
})(i);
}
// 解决方案 2: let(块级作用域)— 推荐
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2, 3, 4
}, 100);
}
// 每次迭代 let 创建新的绑定6. IIFE(立即调用函数表达式)
// 经典 IIFE 形式
(function() {
const privateVar = 'secret';
// 创建独立作用域,避免污染全局
})();
// 带参数的 IIFE
(function($, _) {
// 在内部安全使用 jQuery 和 Lodash
})(jQuery, lodash);
// 箭头函数 IIFE
(() => {
const config = { debug: true };
// ...
})();
// 用于避免变量提升问题
var result = (function() {
var temp = heavyComputation();
return temp * 2;
})();7. 内存注意事项
闭包会使被引用的变量无法被垃圾回收,需要注意内存泄漏。
// 潜在的内存泄漏
function createHeavyClosure() {
const hugeData = new Array(1000000).fill('data');
return function() {
// 即使只用了 hugeData.length,整个 hugeData 数组都被保留
return hugeData.length;
};
}
// 优化:只保留需要的值
function createOptimizedClosure() {
const hugeData = new Array(1000000).fill('data');
const length = hugeData.length; // 提取需要的值
// hugeData 在函数返回后可被 GC
return function() {
return length;
};
}
// DOM 事件中的闭包泄漏
function setupHandler() {
const element = document.getElementById('button');
const heavyData = loadHeavyData();
element.addEventListener('click', function handler() {
console.log(heavyData.summary);
});
// 清理方式:移除事件监听
return function cleanup() {
element.removeEventListener('click', handler);
};
}8. 闭包与 this
闭包捕获的是变量,而非 this 绑定。
const obj = {
name: 'Alice',
// 普通函数:this 取决于调用方式
greet() {
setTimeout(function() {
console.log(this.name); // undefined — this 指向 window/global
}, 100);
},
// 箭头函数:继承定义时的 this
greetArrow() {
setTimeout(() => {
console.log(this.name); // 'Alice' — 箭头函数捕获外层 this
}, 100);
},
// 经典闭包方案
greetClosure() {
const self = this; // 将 this 保存到闭包变量
setTimeout(function() {
console.log(self.name); // 'Alice'
}, 100);
}
};总结
闭包是函数与其词法环境的组合,使得函数能在定义作用域之外访问其中的变量。闭包的核心应用包括数据私有化、工厂函数、柯里化、缓存和模块模式。使用闭包时需要注意循环陷阱(使用 let 或 IIFE 解决)和内存管理(避免不必要地保留大对象引用)。闭包不捕获 this,需要使用箭头函数或显式保存 this 来处理。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于