通过Chrome-DevTools 看js的事件循环

337 阅读3分钟

我们经常刷面试题或者看一些文章,讨论js的执行机制,单线程,事件循环,html的渲染跟js互斥等方面的问题。可是如何来佐证这些,我们一般通过写demo来观察console输出的执行顺序来证明。这次我们通过Chrome-DevTools带的Performance面板,来佐证一下这些问题,让我们更利于理解,不用再死记硬背。

1.首先贴一下代码,demo代码里尽量不要用匿名函数,因为看面板分析时候,可以看到函数名称,易于分析。

<!DOCTYPE html>
<html>
	<head></head>
	<style>
	</style>
	<body>
		<div id="test">
			test
		</div>
	</body>
	<script>
		function test() {
			console.log(1);
		}
		test();
		
		new Promise((resolve, reject) => {
			console.log(2);
			resolve('success')
		}).then(function test_microTask(res){
			console.log(4);
		})
		
		function test2() {
			console.log(3);
		}
		test2();
		setTimeout(function test_settimeout() {
			console.log(5);
		})
		setTimeout(function test_settimeout2() {
			const div = document.createElement('div');
			div.innerHTML = "test2"
			document.getElementById('test').appendChild(div);
		},6)
	</script>
</html>

2.打开DevTools的Performance面板,然后点击“Start profiling and reloadpage” 按钮,得到分析结果。Performance面板得到的信息很多,我们本次只关注Main这个条目。贴一下我的分析结果图,是不是看到了很多熟悉的名词。Main主线程,在Main主线程的这个时间轴上,可以看到一个一个的Task,还可以看到一些诸如Parsehtml、Recalculate Style、Layout、Paint之类的,鼠标滚轮向前放大还会看到Run Microtasks、Compile Script等关键词。

3.我们找个Parsehtml 这个Task,放大后进行分析,如下图。

这个Task,主要做了对document进行parse,包括生成dom树、执行script标签里的同步代码。我们可以看到script部分先进行了“Compile Script”,然后执行了火焰图里背景色为浅绿色的test函数,Promise的构造函数(这是个匿名函数,我没声明函数名称),test2函数。在这些后面才执行了Run Microtasks下的test_microTask函数,就是经常提到的“微任务”部分。

4.在上面这个Task里并没有看到使用settimeout注册的异步回调函数test_settimeout跟test_settimeout2,我们在Main主线程上把时间窗口向后拉一下,在上一个Task后面可以找到。如下图。我们可以看到在单独一个Task里,这也说明了setTimeout,是将异步代码放入到了“宏任务”队列里。跟Promise.then是不一样的,Promise.then是将要执行的代码放入本次Task的最后边,而setTimeout直接新起一个Task。

5.Main时间轴继续后移,找到test_settimeout2,如下图。跟test_settimeout不同的是,因为在这我做了创建一个div元素,并append到#test中。所以dom结构发生改变,因为又需要进行
Parsehtml。dom结构发生改变,肯定还需要重新排版、绘制,在这个Task后面找找,还会有Layout、Paint的Task,如下图2。

总结:通过一个简单的demo我们可以佐证下面一些常见知识点。

1.事件循环,一个Task一个Task的依次执行。

2.“宏任务”Task跟“微任务”MicroTask的区别,微任务是在当前Task的最后边进行执行。

3.js代码的执行之前的编译阶段“Compile Script”

4.看到了html的render部分包括,Parsehtml、Recalculate Style、Layout、Paint等阶段。我们写的js代码的执行跟对html的阶段都是由Main主线程来做。

当然我这个demo比较简单,大家可以再完善一下,可以认识更多,比如看一个document比较大的页面看看Parsehtml那个Task是不是等到document下载完之后才执行。再比如,使用js对一个节点进行隐藏时分别使用display:none 跟 visibility:hidden,看看区别。

Ps:
本文不是一次性写完,Performance面板的有些截图时间轴可能前后对不上。

Performance进行录制时候,不知是否代码太简单,有时候会丢一些调用栈比如test2,如果丢了可以重新录制下。

尽量用chrome的无痕模式,去掉插件代码的干扰。