• 首页
  • 分类
  • 归档
  • 作品
  • 关于

什么是闭包《基础回顾篇》

在 JavaScript 中,每当函数被创建,它就捕获了当时可访问的全部变量,形成一条作用域链。当这个函数被传递到外部并继续执行时,这条作用域链依然有效——这种“函数 + 被捕获的变量”的组合,就是闭包。它既是实现私有状态的简洁手段,也是内存泄漏的常见源头。

垃圾回收机制

首先我们需要有一个概念,垃圾回收机制。JS很方便的通过自动内存管理实现了内存分配和闲置资源回收,无需开发者自行管理。但是,对于开发者来说,需要知道什么时候会清除垃圾,什么时候不会被清除。JS通过标记清理的方式,对所有使用的标量都打上标签,那如果没有标记的变量就会被清除。但是闭包作为一个特殊的存在,只要这个函数还能被外界访问,里面的变量就不会被清除,随着闭包的滥用,可能导致内存泄漏。而必须等到所有地方都用不到这个函数的时候,里面的变量才会被删除。

理解闭包

先说结论,非特殊情况,尽量避免产生闭包。

当内部函数能被外部函数访问的时候,就形成了一个闭包。

最常见的是

function A ()=>{
    const string = 'hello'
    return ()=>{
        console.log(string)
    }
}

A()() //hello

函数中返回了一个函数,那么外层函数作用域下的所有变量就不会被清掉。

当然这只是一种方式,还有像在setTimeout中,或是函数被push到数组中,都属于闭包。

对于闭包来说只是一个概念,并且并不具象,

正确的使用闭包

尽管闭包容易导致内存泄漏,隐式共享,调试困难等问题,但是如果正确使用,能处理很多复杂问题。比如在react中,useState等hooks,为什么能够返回的函数或值,其实就是将状态放到了闭包当中,就算函数重新创建,但是值并不会出现变化。

所以在正常的开发过程中,要注意

function getA ()=>{
    const string = 'hello'
    return ()=>{
        console.log(string)
    }
}

const a = getA()
a()
a= null

当使用完a之后,清除a,则闭包就不存在,也不会导致内存泄露。

// 绑
element.addEventListener('click', handleClick);

// 解绑
element.removeEventListener('click', handleClick);

当事件触发后,同样需要解绑事件,不会导致内存泄漏。

以上是避免,再说一些使用技巧

函数工厂

假设我们需要做一个不同产品,不同折扣的计算器。

// 函数工厂:输入折扣率,输出一个“专属打折函数”
function createDiscount(discount) {
  // discount 被闭包捕获,成为每个返回函数的私有变量
  return function (price) {
    return price * discount;   // 始终用当时捕获的折扣
  };
}

/* --------- 使用 --------- */
const vip80 = createDiscount(0.8); // 8 折
const sale50 = createDiscount(0.5); // 5 折

console.log(vip80(100));   // 80
console.log(sale50(100));  // 50

// 再调用 vip80,它仍记得自己永远是 0.8
console.log(vip80(200));   // 160

更高级的使用闭包——柯里化

如果我想传递多个参数,但又想多次传递,最后一次得到结果。那么可以使用函数柯里化。假设,本来一次要付 3 笔钱,现在变成先付第 1 笔,再付第 2 笔,最后付第 3 笔;每一笔钱都被“收据”记下来,等最后一笔钱到账时一起结算。

// 普通函数:一次给 3 个参数
const add = (a, b, c) => a + b + c;

// 柯里化版本:分 3 次给
const curriedAdd = a => b => c => a + b + c;

curriedAdd(1)(2)(3); // 6

结合闭包

const add1 = curriedAdd(1);   // 先付第 1 笔
add1(2)(3); // 6
add1(4)(5); // 10   1 被复用

总结

柯里化 = “分批传参 + 闭包记账”,让你用更细粒度、可复用的方式拼装函数逻辑。

现在应该就能理解闭包的实际价值。但是还是注意,如果没有典型应用场景,少用闭包。