1. 什么是闭包?
简单理解:闭包就是一个函数能够记住并访问它的外部变量,即使这个函数在其他地方执行
专业定义:闭包是一个函数以及其周围状态(词法环境)的引用的组合
2. 基本闭包示例
function createCounter() {let count = 0 // 这个变量被内部函数所引用,形成闭包return function() {count++ // 内部函数可以访问外部函数的变量console.log(count)}
}const counter = createCounter()
counter() // 输出: 1
counter() // 输出: 2
// 解释:即使 createCounter 已经执行完毕,但 count 变量仍然存在且能被访问
// 因为返回的函数形成了一个闭包,保持着对 count 的引用
3. 闭包的实际应用场景
3.1 数据私有化
function createBankAccount(initialBalance) {let balance = initialBalance // 私有变量return {// 只提供特定的方法来访问和修改余额getBalance: function() {return balance},deposit: function(amount) {balance += amountreturn balance},withdraw: function(amount) {if (amount > balance) {return '余额不足'}balance -= amountreturn balance}}
}const account = createBankAccount(1000)
console.log(account.getBalance()) // 1000
account.deposit(500) // 1500
account.withdraw(200) // 1300
// account.balance // undefined,无法直接访问 balance
3.2 函数工厂(创建具有特定行为的函数)
function multiply(x) {return function(y) {return x * y}
}const multiplyByTwo = multiply(2)
const multiplyByFive = multiply(5)console.log(multiplyByTwo(3)) // 6
console.log(multiplyByFive(3)) // 15
3.3 事件处理和回调
function setupHandler() {let clickCount = 0// 这个函数形成闭包,记住了 clickCountreturn function handler() {clickCount++console.log(`按钮被点击了 ${clickCount} 次`)}
}const handleClick = setupHandler()
// 每次点击都能正确显示点击次数,因为 clickCount 被保存在闭包中
4. 闭包可能导致的问题
4.1 内存泄漏
// 4.1 内存泄漏
function createLeak() {const largeData = new Array(1000000) // 大量数据return function() {// 使用了外部变量,导致 largeData 无法被垃圾回收console.log(largeData.length)}
}
4.2 循环中的常见错误
// 4.2 循环中的常见错误
// 错误示例
for(var i = 0; i < 3; i++) {setTimeout(function() {console.log(i) // 会输出三次 3}, 1000)
}// 正确做法
for(let i = 0; i < 3; i++) {setTimeout(function() {console.log(i) // 会输出 0, 1, 2}, 1000)
}
5.隆重介绍闭包在实际开发中的一些重要应用
1. 防抖 (Debounce)
// 1. 防抖 (Debounce)
// 作用:将多次连续的函数调用合并成一次,常用于搜索框输入、窗口调整等
function debounce(fn, delay) {let timer = null // 使用闭包保存定时器return function(...args) {// 每次触发时,清除之前的定时器if (timer) clearTimeout(timer)// 设置新的定时器timer = setTimeout(() => {fn.apply(this, args)}, delay)}
}// 防抖使用示例
const handleSearch = debounce(function(keyword) {console.log('搜索关键词:', keyword)
}, 300)// 用户快速输入时,只会在最后一次输入后300ms才执行搜索
searchInput.addEventListener('input', (e) => handleSearch(e.target.value))
2. 节流 (Throttle)
// 作用:限制函数在一定时间内只能执行一次,常用于滚动事件、按钮点击等
function throttle(fn, interval) {let lastTime = 0 // 使用闭包保存上次执行时间return function(...args) {const nowTime = Date.now()// 如果距离上次执行的时间大于间隔,则执行函数if (nowTime - lastTime >= interval) {fn.apply(this, args)lastTime = nowTime}}
}// 节流使用示例
const handleScroll = throttle(function() {console.log('页面滚动位置:', window.scrollY)
}, 200)// 滚动时每200ms最多执行一次
window.addEventListener('scroll', handleScroll)
3. 缓存函数结果 (Memoization)
// 3. 缓存函数结果 (Memoization)
// 作用:缓存计算结果,避免重复计算
function memoize(fn) {const cache = {} // 使用闭包保存缓存return function(...args) {const key = JSON.stringify(args)if (key in cache) {console.log('从缓存中获取结果')return cache[key]}console.log('计算新的结果')const result = fn.apply(this, args)cache[key] = resultreturn result}
}// 缓存函数使用示例
const expensiveFunction = memoize((n) => {// 假设这是一个耗时的计算return new Promise(resolve => {setTimeout(() => {resolve(n * 2)}, 1000)})
})
4. 创建自增ID生成器
// 4. 创建自增ID生成器
// 作用:生成唯一ID
function createIdGenerator() {let id = 0 // 使用闭包保存idreturn function() {return ++id}
}const generateId = createIdGenerator()
console.log(generateId()) // 1
console.log(generateId()) // 2
5. 创建一次性执行的函数 (Once)
// 5. 创建一次性执行的函数 (Once)
// 作用:确保函数只执行一次,常用于初始化操作
function once(fn) {let called = false // 使用闭包记录是否调用过let resultreturn function(...args) {if (!called) {result = fn.apply(this, args)called = true}return result}
}// 一次性函数使用示例
const initialize = once(() => {console.log('初始化操作,只执行一次')return { data: 'initialized' }
})
6. 柯里化 (Currying)
6. 柯里化 (Currying)
// 作用:将多参数函数转换为一系列单参数函数
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.concat(moreArgs))}}
}// 柯里化使用示例
const add = curry((a, b, c) => a + b + c)
console.log(add(1)(2)(3)) // 6
console.log(add(1, 2)(3)) // 6
总结:
1.闭包的本质是函数能够访问其定义时的作用域
2.闭包主要用于:
- 数据私有化
- 状态维护
- 函数工厂
- 回调函数
3. 闭包的优点:
- 可以访问外部变量
- 可以维护私有变量
- 可以实现数据封装
4.闭包的注意事项:
- 可能造成内存泄漏
- 创建太多闭包可能影响性能
- 需要注意变量作用域