你不知道的console.log()!

215 阅读3分钟

你不知道的console.log()

可从个人博客中查看

背景

偶然在使用JavaScript做题时发现了一个关于console.log()的坑:

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head><body>
    <script>
        let a = [1, 2, 3];
        console.log(a[0], a);
        a[0] = 0;
        console.log(a[0], a);
    </script>
</body></html>

理论上在浏览器的控制台中会输出:

1 [1,2,3]
0 [0,2,3]

但是,实际上浏览器的输出:

Screenshot 2022-08-12 125419.png

两次输出的数组竟然都是 [0,2,3]

但是在刷新浏览器后:

Screenshot 2022-08-13 104352.png

发现输出结果是正确的,但是在点击下拉框后发现竟然和输出的数组对不上。

而后我将同样的代码使用node进行输出:

Screenshot 2022-08-12 125915.png

发现输出结果又是正确的。

至此可以发现输出结果与运行环境有关,应该是浏览器中console.log()中的问题。

探索过程

然后,通过控制台进行调试,发现在调试过程中,数组a其实发生了变化。这就很奇怪了,为什么调试过程中输出是符合预期的呢?

之后询问了其它大佬,提出过 可以使用JSON.stringify()进行处理,照做之后发现输出结果是正确的:

Screenshot 2022-08-12 130314.png

为什么通过JSON.stringify()进行处理后,输出结果又是正确的呢?

在查找了相关资料后,发现了原因所在。

《你不知道的JavaScript-中卷》 第二部分第一章中写道:

并没有什么规范或一组需求指定 console.* 方法族如何工作——它们并不是 JavaScript 正式的一部分,而是由宿主环境(请参考本书的“类型和语法”部分)添加到 JavaScript 中的。 因此,不同的浏览器和 JavaScript 环境可以按照自己的意愿来实现,有时候这会引起混淆。 尤其要提出的是,在某些条件下,某些浏览器的 console.log(..) 并不会把传入的内容立 即输出。出现这种情况的主要原因是,在许多程序(不只是 JavaScript)中,I/O 是非常低 速的阻塞部分。所以,(从页面 /UI 的角度来说)浏览器在后台异步处理控制台 I/O 能够提高性能,这时用户甚至可能根本意识不到其发生。

书中举了一个例子:

下面这种情景不是很常见,但也可能发生,从中(不是从代码本身而是从外部)可以观察到这种情况:

var a = { index: 1 };
// 然后 
console.log( a );// ?? 
// 再然后 
a.index++;

我们通常认为恰好在执行到 console.log(..) 语句的时候会看到 a 对象的快照,打印出类 似于 { index: 1 } 这样的内容,然后在下一条语句 a.index++ 执行时将其修改,这句的执行会严格在 a 的输出之后。

多数情况下,前述代码在开发者工具的控制台中输出的对象表示与期望是一致的。但是, 这段代码运行的时候,浏览器可能会认为需要把控制台 I/O 延迟到后台,在这种情况下, 等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,因此会显示 { index: 2 }。 到底什么时候控制台 I/O 会延迟,甚至是否能够被观察到,这都是游移不定的。如果在调试的过程中遇到对象在 console.log(..) 语句之后被修改,可你却看到了意料之外的结果, 要意识到这可能是这种 I/O 的异步化造成的.

解决办法

  • 如果遇到这种少见的情况,最好的选择是在 JavaScript 调试器中使用断点, 而不要依赖控制台输出。
  • 次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过 JSON.stringify(..)。