console.log() 会导致内存泄漏? 如何正确的使用 console

1,706 阅读1分钟

console.log()会导致内存泄漏? 应该如何合理的使用 console 呢

实操分析

通常我们会使用 console.log 信息来打印一些调试信息,前不久发现自己在开发时,浏览器频繁崩溃, 恰好又看到三篇分析 console.log 造成内存泄漏的文章,自己在实操分析,并写了一个 babel 小插件后,找到了自己想要的答案,在此和大家分享一下自己的思路,也希望看到不同的观点。

如何去分析?

首先 每 50 ms创建一个包含字符串的对象 obj

const obj = { str: 'g'.repeat(50 * 1024 * 1024) };

并打印他 console.log(obj),

然后 利用performance.memory.totalJSHeapSize获取当前分配的堆内存,

通过内存的大小变化来判断 console.log(obj) ,是否会让对象 obj 保持引用,从为无法进行垃圾回收

代码案例:

 <div id="box"></div>
 ...
const MB = 1024 * 1024;
const box = document.getElementById('box');

const memory = performance.memory.totalJSHeapSize; // 已分配的堆体积
const usagedMemory = Math.floor(memory / MB);
box.insertAdjacentHTML('beforeend', `<span>${usagedMemory} </span>`);
const obj = { usagedMemory, str: 'g'.repeat(50 * MB) }; // 创建包含一个长字符串的对象
console.log(obj);
setTimeout(() => log1(), 50);
  1. 对于console.log(obj)

当开启控制台后,obj始终被引用,无法被垃圾回收

下图演示的表现为:在不开启控制台的情况下执行代码,没开启控制台时,内存大小始终保持不变,在开启控制台后内存一直上升,关闭控制台后内存停止上升

20230206001656.gif 2. if (false) { console.log(obj); }

这个案例可能看起来有点奇怪,一个始终不会执行的代码,为什么还要去测试呢,因为 js 先 “扫描” 再执行的特点,这个案例用来分析代码不会被执行的时候,是否会在 “扫描” 的时候被引用,由下图可见,obj 不会被引用

20230206001755.gif

上述案例代码:consoleLogMemory

结论

当控制台打开时 console.log( x ) 导致对象 x 始终被引用,无法进行垃圾回收,造成内存泄漏

当控制台关闭时 console.log 不会有内存泄漏的问题

不开的时候,devtools frontend没有连接,此时console.log 的信息不会真正发送,而是进入到V8ConsoleMessageStorage里面暂存,而这个暂存池设置了1000条/10MB的上限,超了的话会吐掉最早的信息,因此不会内存爆炸。详见 v8/src/inspector/v8-console-message.cc 里555行 addMessage 的实现

以上的分析均为作者阅读以下三篇文章得来,更多解析见:

如何正确的使用 console ?

在我们得之 console.log 的危害后,如何正常的去使用它呢?

  1. 任何 console 代码都不能在线上存在或执行

    • eslint 检测 console ,在 husky 的 pre-commit 中强制跑 eslint,存在 console,阻止提交
    • terserPlugin 删除所有 console.log
    • 重写覆盖console.log函数:console.log = function (){}
  2. 分环境和情况使用 console

假如想要打印一些 warning、error 信息,或者想要在控制台打印招聘信息 ,此时不能一味的删除 console,可以合理的使用 console 中的 loginfowaringerror 方法 ,此时可以分以下几种情况

首先我们约定两个规则:
1. 测试环境下想打印,而线上不想打印 的调试信息用 log2. 在测试环境和线上环境都想看到的一些信息用`info``waring``error`


遵守上述规则后,用 babel 在 log 外部套一层 if 判断,方可达到想要的效果
   if (process.env.NODE_ENV !== "production") {
          console.log(456)
     }

具体实现见:https://github.com/bowlingQ/babel-plugin-wrapper-condition/
     
 

Babel 详细用法

上述所说用 babel 将所有 console.log 外部包裹一层条件判断,这里提供了一些参数以便更灵活的使用

in

console.log(456)

// wrapper
1 + 1 === 1

out

if (process.env.NODE_ENV !== "production") {
    console.log(456)
}
// wrapper
if (process.env.NODE_ENV !== "production") {
    1 + 1 === 1
}

参数

  • nodeEnv 参考上述案例,他是 env 的一个环境变量,作为条件判断语句 process.env.${nodeEnv} !== "${deployState}"nodeEnv 的占位符。(默认值NODE_ENV)

  • deployState 参考上述案例,他是项目运行的环境值,作为条件判断语句 process.env.${nodeEnv} !== "${deployState}"deployState的占位符。(默认值 production)

  • wrapperlog 是否自动对console.log外部包裹条件判断语句,让其在指定的环境运行。(默认值 true)

  • comment 是否启用注释。参考上述案例,只有他的值为 true 时,有// warpper 注释的表达式才会被包裹条件判断语句,它与commentField 配合使用,注意!它只对表达式有效。(默认值 false)

    表达式 :
        [1,2,3]
        a = 1
        1 + 2;
        -1;
        function(){};
        () => {};
        class{};
        a;
        this;
        super;
        a::b;
    
  • commentField 当注释值包含 commentField 时,它的代码块才会被条件代码包裹。(默认值wrapper)