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);
- 对于
console.log(obj)
当开启控制台后,obj始终被引用,无法被垃圾回收
下图演示的表现为:在不开启控制台的情况下执行代码,没开启控制台时,内存大小始终保持不变,在开启控制台后内存一直上升,关闭控制台后内存停止上升
2.
if (false) { console.log(obj); }
这个案例可能看起来有点奇怪,一个始终不会执行的代码,为什么还要去测试呢,因为 js 先 “扫描” 再执行的特点,这个案例用来分析代码不会被执行的时候,是否会在 “扫描” 的时候被引用,由下图可见,obj 不会被引用
上述案例代码:consoleLogMemory
结论
当控制台打开时 console.log( x ) 导致对象 x 始终被引用,无法进行垃圾回收,造成内存泄漏
当控制台关闭时 console.log 不会有内存泄漏的问题
不开的时候,devtools frontend没有连接,此时console.log 的信息不会真正发送,而是进入到V8ConsoleMessageStorage里面暂存,而这个暂存池设置了1000条/10MB的上限,超了的话会吐掉最早的信息,因此不会内存爆炸。详见 v8/src/inspector/v8-console-message.cc 里555行 addMessage 的实现
以上的分析均为作者阅读以下三篇文章得来,更多解析见:
- 千万别让 console.log 上生产!用 Performance 和 Memory 告诉你为什么
- console.log 一定会导致内存泄漏?不打开 devtools 就不会
- Can console.log() cause memory leaks? How to make a browser crash with console.log()?
如何正确的使用 console ?
在我们得之 console.log 的危害后,如何正常的去使用它呢?
-
任何 console 代码都不能在线上存在或执行
- eslint 检测 console ,在 husky 的 pre-commit 中强制跑 eslint,存在 console,阻止提交
- terserPlugin 删除所有 console.log
- 重写覆盖
console.log函数:console.log = function (){}
-
分环境和情况使用 console
假如想要打印一些 warning、error 信息,或者想要在控制台打印招聘信息 ,此时不能一味的删除 console,可以合理的使用 console 中的 log 、info、waring、error 方法 ,此时可以分以下几种情况
首先我们约定两个规则:
1. 测试环境下想打印,而线上不想打印 的调试信息用 log,
2. 在测试环境和线上环境都想看到的一些信息用`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)