chrome-devtools

前几天,群里有个朋友问了一个很有意思的问题:

function Person (myName, myAge) {
	this.name = myName;
	this.age = myAge;
}
Person.prototype = {
	say:function () {
		console. log(" prototype中的输出");
	}
}
let person = new Person("John" ,12);
console.log(Person.prototype);
console.log(Person.prototype.constructor);
console.log(person.__proto__);

就这段代码而言,第一次运行后打开 Chrome 的 DevTools,会发现打印出来的结果是:

1

而如果在此基础上刷新浏览器,会发现打印的结果变成了这样:

2

这确实是个很不起眼但是很有意思的问题 —— 为什么 Chrome 没有在第一次的时候就直接打印 {say:f},而是像 console.dir 那样打印出一个不具备对象属性预览的 Object

也许这样做是有什么好处,不过先让我们排除一些不必要的干扰因素。第一个是:是否和原型相关?虽然群友给的案例代码涉及到了原型,但其实和原型没有任何关系。我尝试将代码改为简单的 console.log({a:1})后,依然会发生同样的情况 —— 即第一次只打印 Object,刷新之后才打印 {a:1}。第二个是:是否和浏览器相关?目前为止运行代码的环境都是 Chrome,在其它浏览器下打印结果会是怎么样的呢?

在 FireFox 下,发现刷新前后都是直接打印对象属性预览:

3

在 Edge 下,发现刷新前后存在类似 Chrome 的差异:

4

那么,Chrome 这样做的目的是什么呢?带着这个疑问,我先是来到 StackOverflow 提问,不过并没有得到满意的回答。虽然我极力提醒回答者这道题的困惑之处在于刷新前后打印结果的差异,但他还是“跑题”了…不过,他的回答中有一个地方引起了我的注意,就是“ a very slow operation”。这确实是给出了一个思考的方向:Chrome 在一开始没有直接打印对象的预览,会不会是因为这是一个耗时操作呢?所以,也许这是一个性能相关的问题?

接着我尝试到知乎提问,最终很惊喜地得到了大佬的回复 —— 这确实是一个为了性能优化而采取的行为:

点进回答里提供的链接看一下,有更加详细的解释:

简单地说,这个行为的差异不是由于刷新浏览器导致的,而是由于打开 DevTools 导致的。我们在第一次运行代码之后,对象就打印出来了,但此时还没有打开 DevTools,所以这部分打印的内容是暂时放在内存的缓冲区(buffer)中的。而且对一个普通的用户来说,他很可能永远也不需要打开 DevTools,在这种情况下若仍然选择呈现预览对象,会对内存和 CPU 有一定的要求,考虑到这一点,在设计上会让这次的打印不呈现预览对象。

对我们来说,如果这一次打开 DevTools,我们看到的就只会是 Object。但是,如果在打开之后再次刷新,那么我们看到的就是所期望的 {a:1}。因此,这种行为差异是在“保留信息”和“减少内存占用”之间所做的权衡(trade-off)。

我们还可以进一步验证一下:随便打开一个页面,并且打开控制台,然后把代码文件直接拖到该页面运行:

这时候会发现,控制台里是直接打印出 {a:1} 这样的预览对象的,这是因为在打印之前我们就提前把 DevTools 打开了,这时候打印的对象并不会放在缓冲区中。

此外,回答里还提到一个叫做 ObjectPreview 的东西,它其实就是上面所讲的能够呈现对象属性预览的东西,实际上是 cdp( ChromeDevToolsProtocol ) 协议的一个 api。cdp 协议允许我们检测和调试 Chrome 浏览器,我们所熟知的 ChromeDevTools 就是遵循这个协议的。从这点来说,当我们打开 DevTools 时,其实就已经在使用 cdp 协议了。

FireFox 可能认为这个优化对性能提升并不是很明显,所以在设计上选择的是直接打印 ObjectPreview。当然不排除还有其它方面的考虑,具体的我就没有再深挖啦,毕竟咱也不是开发浏览器的,了解一下,知道有这么一个东西就好了。关键的是,这次的疑惑得到了一个比较官方而准确的解答,我认为这才是最大的收获。

参考:

https://www.zhihu.com/question/403754007

https://twitter.com/ziyunfei/status/1277126750569328645

https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ObjectPreview