在前端代码中经常会看到使用
setTimeout(fn, 0)
,虽然看起来多余,但移除它可能会导致一些奇怪的问题。为了解释这一点,我们需要理解
事件循环(Event Loop)
。下面将通过一些例子和动画来辅助理解事件循环。
setTimeout(() => {
// 调用一些方法
}, 0)
JavaScript是单线程的(浏览器和Node则是多线程的)。为了避免
渲染主线程
阻塞,需要采用异步方式,而
事件循环
就是异步的实现方式。
浏览器在一个渲染主线程中运行一个页面中的所有JavaScript脚本,以及呈现布局、回流和垃圾回收。为了避免
同步
的执行方式导致渲染主线程阻塞,使得页面卡死,浏览器采用异步的方式:渲染主线程将任务交给其他线程去处理,自身
立即结束
任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到对应的消息队列的
末尾
排队,等待渲染主线程调度执行。
流程:
setTimeout
延时队列
任务没有优先级,而消息队列有优先级,不同任务分属于不同队列:参考W3C规范。微队列优先级最高,接着是交互队列,然后才是延时队列。
常见队列:
Promise.resolve().then()
下面例子来自于《WEB前端大师课》,大块的文字描述相对没那么直观,所以用Keynote做了gif方便理解(如果有更好的做gif的方式可以留言告诉我)。
JS修改了DOM后,并不会马上显示在页面上,需要进行
绘制
后才会显示页面变更。
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<h1>初始h1</h1>
<button>change</button>
<script>
var h1 = document.querySelector('h1');
var btn = document.querySelector('button');
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {}
}
btn.onclick = function () {
h1.textContent = '修改h1 textContent';
delay(3000);
};
</script>
</body>
</html>
效果:点击
change
后,页面卡死,3s后
h1
内容变更为:
修改h1 textContent
setTimeout
到达指定时间可能并不会立即执行。
setTimeout(function () {
console.log(1);
}, 0);
function delay(duration) {
var start = Date.now();
while (Date.now() - start < duration) {}
}
delay(3000);
console.log(2);
效果:卡死3s后输出2 1
使用
Promise.resolve().then
可以将任务直接添加到微队列。
setTimeout(function () {
console.log(1);
}, 0);
Promise.resolve().then(function () {
console.log(2);
});
console.log(3);
效果:依次输出3 2 1
function a() {
console.log(1);
Promise.resolve().then(function () {
console.log(2);
});
}
setTimeout(function () {
console.log(3);
Promise.resolve().then(a);
}, 0);
Promise.resolve().then(function () {
console.log(4);
});
console.log(5);
效果:依次输出5 4 3 1 2
理解了上面的概念,可以尝试分析一下现代JavaScript教程事件循环例子,检查一下是否理解了事件循环。
2024年我还在写这样的代码
为什么JS要加入setTimeout,CSS的transition才能生效
深入理解和使用JavaScript中的setTimeout(fn,0)
主线程
并发模型与事件循环
异步JavaScript
调度:setTimeout和setInterval