- 发布于
JavaScript 闭包机制深度解析:词法作用域、内存管理与实际应用
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
JavaScript 闭包机制深度解析:词法作用域、内存管理与实际应用
闭包是 JavaScript 中最重要也是最容易被误解的概念之一。它不仅是语言的核心特性,更是许多高级编程模式的基础。本文将深入探讨闭包的工作原理和实际应用。
闭包的基本概念
什么是闭包
// 闭包的基本形式
function outerFunction(x) {
// 外部函数的变量
const outerVariable = x
// 内部函数
function innerFunction(y) {
// 内部函数访问外部函数的变量
console.log(`外部变量: ${outerVariable}, 内部参数: ${y}`)
return outerVariable + y
}
// 返回内部函数
return innerFunction
}
// 创建闭包
const closure = outerFunction(10)
// 调用闭包
console.log(closure(5)) // 输出: 外部变量: 10, 内部参数: 5, 返回: 15
// 即使外部函数已经执行完毕,内部函数仍然可以访问外部变量
console.log(closure(20)) // 输出: 外部变量: 10, 内部参数: 20, 返回: 30
词法作用域和作用域链
// 词法作用域演示
const globalVar = 'global'
function level1() {
const level1Var = 'level1'
function level2() {
const level2Var = 'level2'
function level3() {
const level3Var = 'level3'
// 内部函数可以访问所有外层作用域的变量
console.log('全局变量:', globalVar)
console.log('Level1 变量:', level1Var)
console.log('Level2 变量:', level2Var)
console.log('Level3 变量:', level3Var)
// 返回一个函数,形成闭包
return function () {
return {
global: globalVar,
level1: level1Var,
level2: level2Var,
level3: level3Var,
}
}
}
return level3()
}
return level2()
}
// 创建闭包
const deepClosure = level1()
// 即使所有外层函数都已执行完毕,闭包仍然保持对变量的引用
console.log(deepClosure())
// 输出: { global: 'global', level1: 'level1', level2: 'level2', level3: 'level3' }
// 作用域链查找演示
function scopeChainDemo() {
const a = 1
function inner1() {
const b = 2
function inner2() {
const c = 3
const a = 'inner2' // 遮蔽外层的 a
console.log('a:', a) // 'inner2' (最近的 a)
console.log('b:', b) // 2 (从 inner1 作用域)
console.log('c:', c) // 3 (当前作用域)
// 访问全局作用域需要使用 window (浏览器) 或 global (Node.js)
// console.log('outer a:', window.a) // 如果 a 是全局变量
}
return inner2
}
return inner1()
}
const scopeClosure = scopeChainDemo()
scopeClosure()
闭包的形成条件
不同情况下的闭包形成
// 1. 函数返回函数
function createCounter() {
let count = 0
return function () {
return ++count
}
}
const counter1 = createCounter()
const counter2 = createCounter()
console.log(counter1()) // 1
console.log(counter1()) // 2
console.log(counter2()) // 1 (独立的闭包)
console.log(counter1()) // 3
// 2. 函数作为参数传递
function processArray(arr, callback) {
const multiplier = 2
return arr.map(function (item, index) {
// callback 形成闭包,可以访问外部变量
return callback(item * multiplier, index)
})
}
function createProcessor(prefix) {
// 返回的函数形成闭包
return function (value, index) {
return `${prefix}[${index}]: ${value}`
}
}
const processor = createProcessor('Item')
const result = processArray([1, 2, 3], processor)
console.log(result)
// 输出: ['Item[0]: 2', 'Item[1]: 4', 'Item[2]: 6']
// 3. 事件处理器中的闭包
function setupEventHandlers() {
const buttons = document.querySelectorAll('.btn')
const clickCounts = new Map()
buttons.forEach((button, index) => {
clickCounts.set(button, 0)
// 事件处理器形成闭包
button.addEventListener('click', function () {
const currentCount = clickCounts.get(button) + 1
clickCounts.set(button, currentCount)
console.log(`按钮 ${index} 被点击了 ${currentCount} 次`)
// 可以访问外部作用域的变量
button.textContent = `点击次数: ${currentCount}`
})
})
// 返回清理函数
return function cleanup() {
buttons.forEach((button) => {
button.removeEventListener('click', arguments.callee)
})
clickCounts.clear()
}
}
// 4. 定时器中的闭包
function createTimer(name, interval) {
let seconds = 0
const timerId = setInterval(function () {
seconds++
console.log(`${name}: ${seconds} 秒`)
// 闭包保持对外部变量的引用
if (seconds >= 5) {
clearInterval(timerId)
console.log(`${name} 计时器结束`)
}
}, interval)
// 返回控制函数
return {
stop: function () {
clearInterval(timerId)
console.log(`${name} 计时器被手动停止`)
},
getSeconds: function () {
return seconds
},
}
}
const timer = createTimer('测试计时器', 1000)
// 5. 模块模式中的闭包
const modulePattern = (function () {
// 私有变量
let privateVar = 0
const privateArray = []
// 私有方法
function privateMethod() {
console.log('这是私有方法')
}
// 返回公共接口
return {
// 公共方法通过闭包访问私有变量
increment: function () {
privateVar++
return privateVar
},
decrement: function () {
privateVar--
return privateVar
},
getValue: function () {
return privateVar
},
addItem: function (item) {
privateArray.push(item)
privateMethod() // 调用私有方法
return privateArray.length
},
getItems: function () {
// 返回副本,保护私有数据
return [...privateArray]
},
}
})()
console.log(modulePattern.increment()) // 1
console.log(modulePattern.getValue()) // 1
console.log(modulePattern.addItem('test')) // 1
console.log(modulePattern.getItems()) // ['test']
闭包的实际应用
1. 数据封装和私有变量
// 创建具有私有状态的对象
function createBankAccount(initialBalance) {
let balance = initialBalance
const transactionHistory = []
// 私有方法
function addTransaction(type, amount) {
transactionHistory.push({
type,
amount,
balance: balance,
timestamp: new Date(),
})
}
// 返回公共接口
return {
deposit: function (amount) {
if (amount > 0) {
balance += amount
addTransaction('deposit', amount)
return balance
}
throw new Error('存款金额必须大于0')
},
withdraw: function (amount) {
if (amount > 0 && amount <= balance) {
balance -= amount
addTransaction('withdraw', amount)
return balance
}
throw new Error('取款金额无效或余额不足')
},
getBalance: function () {
return balance
},
getTransactionHistory: function () {
// 返回副本,防止外部修改
return transactionHistory.map((t) => ({ ...t }))
},
// 只读属性
get accountInfo() {
return {
balance: balance,
transactionCount: transactionHistory.length,
lastTransaction: transactionHistory[transactionHistory.length - 1],
}
},
}
}
const account = createBankAccount(1000)
console.log(account.deposit(500)) // 1500
console.log(account.withdraw(200)) // 1300
console.log(account.getBalance()) // 1300
console.log(account.accountInfo) // 账户信息
// 无法直接访问私有变量
console.log(account.balance) // undefined
2. 函数工厂和柯里化
// 函数工厂
function createValidator(rules) {
return function (value) {
const errors = []
// 闭包访问 rules 参数
for (const rule of rules) {
if (!rule.test(value)) {
errors.push(rule.message)
}
}
return {
isValid: errors.length === 0,
errors: errors,
}
}
}
// 创建不同的验证器
const emailValidator = createValidator([
{
test: (value) => typeof value === 'string',
message: '必须是字符串',
},
{
test: (value) => value.includes('@'),
message: '必须包含@符号',
},
{
test: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
message: '邮箱格式不正确',
},
])
const passwordValidator = createValidator([
{
test: (value) => typeof value === 'string',
message: '必须是字符串',
},
{
test: (value) => value.length >= 8,
message: '密码长度至少8位',
},
{
test: (value) => /[A-Z]/.test(value),
message: '必须包含大写字母',
},
{
test: (value) => /[0-9]/.test(value),
message: '必须包含数字',
},
])
console.log(emailValidator('test@example.com'))
console.log(passwordValidator('Password123'))
// 柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
return function (...nextArgs) {
return curried.apply(this, args.concat(nextArgs))
}
}
}
}
// 使用柯里化
const add = (a, b, c) => a + b + c
const curriedAdd = curry(add)
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1)(2, 3)) // 6
// 实用的柯里化应用
const multiply = curry((a, b) => a * b)
const double = multiply(2)
const triple = multiply(3)
console.log(double(5)) // 10
console.log(triple(5)) // 15
const numbers = [1, 2, 3, 4, 5]
console.log(numbers.map(double)) // [2, 4, 6, 8, 10]
console.log(numbers.map(triple)) // [3, 6, 9, 12, 15]
3. 缓存和记忆化
// 记忆化函数
function memoize(fn) {
const cache = new Map()
return function (...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('从缓存获取结果')
return cache.get(key)
}
console.log('计算新结果')
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// 斐波那契数列(递归版本)
const fibonacci = memoize(function (n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
})
console.log(fibonacci(10)) // 计算并缓存
console.log(fibonacci(10)) // 从缓存获取
console.log(fibonacci(15)) // 利用之前的缓存结果
// 带过期时间的缓存
function createCacheWithTTL(ttl = 60000) {
// 默认1分钟
const cache = new Map()
return function memoizeWithTTL(fn) {
return function (...args) {
const key = JSON.stringify(args)
const now = Date.now()
if (cache.has(key)) {
const { value, timestamp } = cache.get(key)
if (now - timestamp < ttl) {
console.log('从缓存获取结果')
return value
} else {
console.log('缓存已过期,删除缓存')
cache.delete(key)
}
}
console.log('计算新结果')
const result = fn.apply(this, args)
cache.set(key, { value: result, timestamp: now })
return result
}
}
}
const memoizeWithTTL = createCacheWithTTL(5000) // 5秒过期
const expensiveOperation = memoizeWithTTL(function (x, y) {
// 模拟耗时操作
let result = 0
for (let i = 0; i < 1000000; i++) {
result += x * y
}
return result
})
console.log(expensiveOperation(10, 20))
console.log(expensiveOperation(10, 20)) // 从缓存获取
4. 事件系统和观察者模式
// 使用闭包实现事件系统
function createEventEmitter() {
const events = new Map()
return {
on: function (eventName, callback) {
if (!events.has(eventName)) {
events.set(eventName, [])
}
events.get(eventName).push(callback)
// 返回取消订阅的函数
return function unsubscribe() {
const callbacks = events.get(eventName)
if (callbacks) {
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
}
},
emit: function (eventName, ...args) {
const callbacks = events.get(eventName)
if (callbacks) {
callbacks.forEach((callback) => {
try {
callback(...args)
} catch (error) {
console.error('事件处理器执行错误:', error)
}
})
}
},
once: function (eventName, callback) {
const unsubscribe = this.on(eventName, function (...args) {
callback(...args)
unsubscribe() // 执行一次后自动取消订阅
})
return unsubscribe
},
off: function (eventName) {
events.delete(eventName)
},
getEventNames: function () {
return Array.from(events.keys())
},
getListenerCount: function (eventName) {
const callbacks = events.get(eventName)
return callbacks ? callbacks.length : 0
},
}
}
// 使用事件系统
const emitter = createEventEmitter()
// 订阅事件
const unsubscribe1 = emitter.on('user:login', (user) => {
console.log(`用户 ${user.name} 登录了`)
})
const unsubscribe2 = emitter.on('user:login', (user) => {
console.log(`记录登录日志: ${user.name} at ${new Date()}`)
})
// 一次性事件
emitter.once('app:ready', () => {
console.log('应用已准备就绪')
})
// 触发事件
emitter.emit('user:login', { name: 'John', id: 1 })
emitter.emit('app:ready')
emitter.emit('app:ready') // 不会再次触发
// 取消订阅
unsubscribe1()
emitter.emit('user:login', { name: 'Jane', id: 2 }) // 只有第二个监听器会执行
内存管理和性能考虑
闭包的内存影响
// 内存泄漏示例和解决方案
function createMemoryLeakExample() {
const largeData = new Array(1000000).fill('large data')
let timer
return {
// ❌ 可能导致内存泄漏
startBadTimer: function () {
timer = setInterval(function () {
// 即使不使用 largeData,闭包仍然保持对它的引用
console.log('定时器运行中...')
}, 1000)
},
// ✅ 更好的做法
startGoodTimer: function () {
// 只保留需要的数据
const dataSize = largeData.length
timer = setInterval(function () {
console.log(`定时器运行中... 数据大小: ${dataSize}`)
}, 1000)
// 释放大数据的引用
// largeData = null // 注意:这样做在严格模式下会报错
},
stop: function () {
if (timer) {
clearInterval(timer)
timer = null
}
},
}
}
// 内存优化的闭包使用
function createOptimizedClosure() {
let cache = new Map()
let isDestroyed = false
function checkDestroyed() {
if (isDestroyed) {
throw new Error('对象已被销毁')
}
}
return {
set: function (key, value) {
checkDestroyed()
cache.set(key, value)
},
get: function (key) {
checkDestroyed()
return cache.get(key)
},
// 提供清理方法
destroy: function () {
cache.clear()
cache = null
isDestroyed = true
},
// 获取内存使用情况
getMemoryInfo: function () {
checkDestroyed()
return {
size: cache.size,
keys: Array.from(cache.keys()),
}
},
}
}
// 弱引用闭包(使用 WeakMap)
function createWeakReferenceClosure() {
const weakCache = new WeakMap()
return {
associate: function (obj, data) {
weakCache.set(obj, data)
},
getAssociated: function (obj) {
return weakCache.get(obj)
},
hasAssociation: function (obj) {
return weakCache.has(obj)
},
}
}
// 使用示例
const weakClosure = createWeakReferenceClosure()
let obj = { id: 1 }
weakClosure.associate(obj, { metadata: 'some data' })
console.log(weakClosure.getAssociated(obj)) // { metadata: 'some data' }
// 当 obj 被垃圾回收时,WeakMap 中的关联也会自动清除
obj = null
性能优化建议
// 性能优化的闭包模式
function createPerformantClosure() {
// 1. 避免在闭包中保存不必要的引用
const config = {
/* 配置对象 */
}
// 2. 使用对象池减少内存分配
const objectPool = []
function getPooledObject() {
return objectPool.pop() || {}
}
function returnToPool(obj) {
// 清理对象
Object.keys(obj).forEach((key) => delete obj[key])
objectPool.push(obj)
}
// 3. 延迟初始化
let expensiveResource = null
function getExpensiveResource() {
if (!expensiveResource) {
expensiveResource = createExpensiveResource()
}
return expensiveResource
}
return {
processData: function (data) {
const obj = getPooledObject()
try {
// 处理数据
obj.result = data.map((item) => item * 2)
return obj.result
} finally {
returnToPool(obj)
}
},
useExpensiveResource: function () {
return getExpensiveResource().doSomething()
},
}
}
function createExpensiveResource() {
console.log('创建昂贵的资源')
return {
doSomething: () => 'expensive operation result',
}
}
总结
JavaScript 闭包的核心要点和最佳实践:
🎯 核心概念
- 词法作用域:函数在定义时确定作用域
- 变量捕获:内部函数保持对外部变量的引用
- 生命周期延长:外部变量不会被垃圾回收
- 独立实例:每次调用外部函数都创建新的闭包
✅ 实际应用
- 数据封装和私有变量
- 模块模式和命名空间
- 函数工厂和柯里化
- 事件处理和回调函数
- 缓存和记忆化
⚠️ 注意事项
- 避免不必要的闭包创建
- 及时清理引用防止内存泄漏
- 注意循环中的闭包陷阱
- 合理使用 WeakMap 和 WeakSet
🚀 性能优化
- 避免在闭包中保存大对象
- 提供清理方法
- 使用对象池减少内存分配
- 延迟初始化昂贵资源
掌握闭包,你就掌握了 JavaScript 的精髓!
闭包是 JavaScript 最强大的特性之一,理解它能让你写出更优雅、更强大的代码。