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.闭包的注意事项:

可能造成内存泄漏创建太多闭包可能影响性能需要注意变量作用域