为什么写这篇文章?
js线程与浏览器ui线程之间的执行关系一直是我的一个心结,今天在研究vue真实dom、浏览器真实dom的关系过程中,在参考他人文献中突然开窍,特此写下该文章,该文章适合有eventLoop
基础认识的人士阅读。
突破口: UI线程的工作基于一个简单的队列系统,任务会保存到ui更新队列
中。
基础知识: js线程优先级大于ui线程
关于事件循环的执行顺序: callStack => 微任务队列 => ui更新队列任务 => 宏任务
用一个例子来解答:
<button onclick="handleClick()">Click me</button>
<script type="text/javascript">
function handleClick() {
var div = document.createElement('div');
div.innerHTML = 'good work';
document.body.appendChild(div);
}
</script>
当示例中的按钮被点击时,它会触发UI线程来创建两个任务并添加到队列中。第一个任务是更新按钮的UI,它需要改变外观以表示被点击了,第二个任务是执行JS代码即handlerClick()中的代码,唯一被运行的代码是这个方法和所有被它调用的方法。假设现在UI线程处于空闲状态,第一个任务被提取出来并执行以便更新按钮的外观,然后JavaScript任务被提取出来并执行。在运行过程中,handleClick()创建了一个新的<div>
元素并把它附加在<body>
元素末尾,这实际引发另一个UI变化。意味着在JavaScript执行过程中,一个新的UI更新任务被添加到队列中,当JavaScript运行完之后,UI还会再更新一次页面。
当脚本执行时,UI不随用户交互而更新。执行时间段内用户交互行为(比如点击事件)所引发的JavaScript任务被加入队列中,并在最初的JavaScript任务完成后依次执行。而这段时间内由用户交互行为所引发的UI更新会被自动跳过,因为页面中的动态部分会被优先考虑。因此,在一个脚本运行期间点击一个按钮,将无法看到它被按钮按下的样式,尽管它的onclick事件处理器会被执行。
延伸问题
1.为什么vue的真实dom操作会优先选择微任务呢?
答:因为微任务在ui更新队列之前,每一个真实dom操作都会给ui更新队列中推入一个新的任务,所以在拉起ui线程的时候,只需要拉取一次清空ui线程中的任务就行,如果放在宏任务的话,每一次宏任务所生产的ui更新队列任务都会造成一次ui线程的拉起,如果有一百个宏任务dom操作就会拉起100次ui线程,这样会对性能造成极大的影响。
补充证明执行循序的代码方案
判断宏任务与ui线程执行顺序
document.getElementsByTagName('h1')[0].addEventListener('click',function(){
setTimeout(() => {
while(true){
console.log(1)
}
});
this.style.color="red"
})
// 执行结果:元素变红,然后进入while循环打印 1
判断微任务与ui线程执行顺序
document.getElementsByTagName('h1')[0].addEventListener('click',function(){
Promise.resolve(2).then(res => {
while(true){
console.log(res)
}
}
this.style.color="red"
})
// 执行结果:进入while循环打印 2