闭包函数的内存管理

聊聊闭包函数的内存占用、垃圾回收机制的作用,以及简单的内存清理策略

闭包变量的内存占用

javascript 本身由于有垃圾回收机制,垃圾回收机制(GC)主要依赖于可达性原则来管理内存。可达性的基本原则是,如果一个对象能够通过某些路径从根对象(如全局对象或当前执行上下文的变量)访问到,那么这个对象就是可达的,不会被回收。否则,它就是不可达的,会被垃圾回收。虽然 GC 简化了内存管理,但并不意味着开发者不需要对内存进行显示管理。

例如,在开发 react 组件时,使用定时器等资源时,常常会通过 useEffect 来 set 和 clear

useEffect(() => {
  const timer = setInterval(() => {
    // do something
  })
  return () => clearInterval(timer)
}

对于占用了比较大的内存的变量而不需要全局存在,通常是使用 letconst 而不是 var

lodash 中有个函数 memoize,可以用来缓存函数的计算结果。源码如下:

function memoize(func, resolver) {
    if (typeof func !== 'function' || (resolver != null && typeof resolver !== 'function')) {
        throw new TypeError('Expected a function');
    }
    const memoized = function (...args) {
        const key = resolver ? resolver.apply(this, args) : args[0];
        const cache = memoized.cache;
    if (cache.has(key)) {
        return cache.get(key);
    }
    const result = func.apply(this, args);
    memoized.cache = cache.set(key, result) || cache;
    return result;
};
memoized.cache = new (memoize.Cache || Map)();
return memoized;

}

memoize.Cache = Map;

export default memoize;

本质上是返回了一个闭包函数,该函数使用了一个闭包变量 memoized.cache,另外一种实现方式是直接在闭包中定义 cache 局部变量:

// wo zi ji xie de
function memoize(func, resolver) {
    ···
    let cache = new (memoize.Cache || Map)();
    const memoized = function (...args) {
       ···
    };
    return memoized;
}

闭包变量的垃圾回收

在这两种实现中,内存回收的处理是等价的。当返回的 memorized 函数的引用计数为 0 时,闭包变量随之也会被回收。

尽管依赖于 gc 自动垃圾回收,还是可能会发生两个问题:

  1. 外部变量引用了 Cache,此时如果 memorized 被回收了,而 Cache 本该被回收但实际没有被回收,导致内存泄漏。
  2. 如果 Cache 中的某个元素被其他变量引用,该元素不会被回收,因为它仍然是可达的。

可达性的基本原则是,如果一个对象能够通过某些路径从根对象(如全局对象或当前执行上下文的变量)访问到,那么这个对象就是可达的,不会被回收。否则,它就是不可达的,会被垃圾回收。

手动清理内存

为了避免上述内存泄漏问题,可以手动清理内存:

清除整个缓存:memoized.cache.clear() 将缓存置为空:memoized.cache = null 结论 虽然 JavaScript 的垃圾回收机制简化了内存管理,但在使用闭包和缓存时,开发者仍需显式地管理内存以避免内存泄漏。通过合理设计内存清理机制,可以有效管理内存占用,确保应用程序的稳定性和性能。

与之相关的还有 map 和 weakmap,react-cache,后面再写写。


这是一个从 https://juejin.cn/post/7368640074382819379 下的原始话题分离的讨论话题