《闭包:从柯里化到内存泄漏,开发中的双刃剑》

鼠鼠在旁观了一些面试后觉得,闭包这个知识点经常被面试官提到,所以我想在这里做一些总结。

闭包的定义

闭包(Closure)是指一个函数与其引用环境(lexical environment)的组合。具体来说:

  • 函数内部定义的函数​(内部函数)可以访问外部函数的变量。
  • 即使外部函数已经执行完毕,内部函数仍能保留对外部函数作用域的引用,从而“记住”这些变量。

用我们自己的话说就是,内部函数可以访问到外部函数但是外部函数却不能访问到内部函数中的变量。

我觉得,闭包被经常提及的原因是我们在开发中采用的模块化思想,它的基础就是闭包。只有模块之间互相不影响,才能实现模块化。因此了解闭包的机制是开发的基础。


闭包的核心机制

  1. 词法作用域(Lexical Scoping)​
    函数在定义时确定作用域链,而非执行时。内部函数会捕获其外部函数的变量,形成闭包。

  2. 环境保留
    当外部函数执行结束后,其作用域本应销毁,但闭包会保留对这些变量的引用,使其不被垃圾回收。

代码示例:

function createCounter() {
  let count = 0; // 外部函数的变量
  return function() {
    count++;     // 内部函数访问外部变量
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 输出1
counter(); // 输出2(count状态被保留)

关键点

  • 形成条件
    内部函数引用外部函数的变量,且被外部函数返回或在其他作用域中被调用。

  • 内存管理
    闭包可能导致内存泄漏(如未及时释放不再需要的变量),需谨慎使用。


闭包的应用场景

封装私有变量
隐藏实现细节,暴露有限接口(模块模式)这样不但能实现私有化还能避免变量污染。

const module = (function() {
  let privateVar = 0;
  return { increment: () => privateVar++ };
})();

柯里化(Currying)​
函数分步传递参数,延迟执行。

const multiply = a => b => a * b;
const double = multiply(2);
double(5); // 10

事件处理与回调
保留上下文状态,如记录点击次数。

button.addEventListener('click', (function() {
  let clicks = 0;
  return () => console.log(`点击次数:${++clicks}`);
})());

闭包的缺点

使用闭包能帮助我们实现很多功能,但是过多的使用闭包会带来缺点风险

1. 内存泄漏风险

闭包会长期持有外部函数的变量引用,导致这些变量无法被垃圾回收(即使外部函数已执行完毕)。

function createHeavyObject() {
  const bigData = new Array(1000000).fill('data'); // 占用内存的大对象
  return () => console.log(bigData.length); // 闭包引用 bigData
}

const closure = createHeavyObject();
// 即使不再需要 bigData,闭包仍持有引用,内存无法释放

 在这个例子中,这么大的数据,我们不再需要时,但是垃圾回收机制却无法回收就是因为它在闭包中被引用了

2. 性能损耗

闭包的作用域链查找比局部变量更慢,频繁访问外部变量可能影响性能。

3. 变量误用

闭包中的变量是“静态”的,可能因延迟执行导致意外结果(常见于循环中)。

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}

 4.跨浏览器兼容性

某些浏览器(如旧版 IE)的垃圾回收机制不完善,闭包可能导致内存无法释放。


如何规避使用闭包的风险

规避闭包带来的缺点

1. 及时解除引用

当闭包不再使用时,手动将闭包变量设为 null,以释放内存。这是因为闭包会持有对外部作用域变量的引用,如果不及时解除引用,这些变量将无法被垃圾回收机制回收,从而导致内存泄漏。

2. 减少闭包的创建

避免在循环或频繁调用的函数中创建闭包,因为每次创建闭包都会带来一定的内存开销和性能损耗。例如,在下面的代码中,每次循环都创建一个新的闭包,这会导致内存开销增加:

3. 使用WeakMap

当需要在闭包中存储大量数据时,可以考虑使用WeakMapWeakMap是一种特殊的映射类型,它的键值对不会阻止垃圾回收器回收键所指向的对象。


 

总结

闭包的知识就集中在利用js里块级作用域实现,闭包可以被广泛用于异步变成和模块化。


《闭包:从柯里化到内存泄漏,开发中的双刃剑》_第1张图片

“开始亦是结束的开始” 

——《花束般的恋爱》 

你可能感兴趣的:(面试精选,javascript,开发语言,ecmascript,前端)