一、事件循环
先说原理,浏览器渲染主进程中只有一个渲染主线程,这个主线程即js的单线程。主线程承担着诸多的工作,渲染页面、执行JS都在其中运行。事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。浏览器的任务存在消息队列当中,每次主线程从消息队列中取出第一个任务执行,执行完毕后再去消息队列中取任务…而在执行当前任务时,可能会产生新的任务,这些新的任务会被添加到消息队列中等待执行。比如计时器时间到了,用户触发了监听事件,浏览器触发了重新渲染,这些任务不会被立马执行,而是等到主线程把当前任务完成才会去执行,这就导致了js阻碍浏览器的渲染,下面看一个例子。
二、举例
<body>
<button onclick="changeText(this)">改变文字</button>
</body>
<script>
function sleep(time) { //辅助函数,阻塞主线程
let start = Date.now();
while (Date.now() - start < time) { }
}
function changeText(that) {
that.innerText = '文字已改变';
console.log(that.innerText);
sleep(3000);
}
</script>
在这个例子中,为按钮绑定了单击事件,触发的时候改变按钮的文字,并且打印改变后的文字内容,再阻塞主线程3秒钟。一般来说,当dom的innerText属性被改变时,浏览器也会即刻展现,这样来说,阻塞3秒的函数写在这里就没什么用了。但事实并非如此,下面看执行结果。
三、分析结果
可以看到,当点击按钮后,控制台立即打印了按钮的innerText,而3秒过后,浏览器才进行渲染。这是因为主线程将js作为全局任务来执行,当改变按钮的innerText属性时,产生了额外的渲染任务,此任务不会被立刻执行,而是被添加到消息队列中,等待主线程将它取出执行。虽然立刻改变了按钮的innerText属性值,但是此时js代码全局任务并没有执行完,还需要执行阻塞3秒的sleep函数。当执行完js所有代码后,主线程才回去消息队列中取出渲染任务,将其渲染到浏览器中。所以总结一句话,js会阻碍浏览器的渲染。