2025-09-23 06:34:27详细讲一下什么是闭包,为什么会产生闭包,闭包会导致什么,闭包可以帮助我们在开发中干什么
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 += amount
return balance
},
withdraw: function(amount) {
if (amount > balance) {
return '余额不足'
}
balance -= amount
return 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
// 这个函数形成闭包,记住了 clickCount
return 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] = result
return result
}
}
// 缓存函数使用示例
const expensiveFunction = memoize((n) => {
// 假设这是一个耗时的计算
return new Promise(resolve => {
setTimeout(() => {
resolve(n * 2)
}, 1000)
})
})
4. 创建自增ID生成器
// 4. 创建自增ID生成器
// 作用:生成唯一ID
function createIdGenerator() {
let id = 0 // 使用闭包保存id
return 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 result
return 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.闭包的注意事项:
可能造成内存泄漏创建太多闭包可能影响性能需要注意变量作用域