事件循环很有趣的讲解视频:www.bilibili.com/video/BV1K4…
唠嗑总结
js运行 和 页面渲染的顺序是确定的,归功于事件环
网页有一个叫做 主线程 的东西----**main thread,**js 运行,渲染 都发生在主线程上 ,
对于一些异步任务,可能需要很长时间,
如果放在主线程上执行,则会阻塞主线程上其他的事件例如 渲染、页面交互。
所以,还需要其他的辅助线程,把异步任务放在辅助线程上,处理完后再通知主线程。
而事件环是用来协调所有这些活动。
以 setTimeout 为例,run in parallel,并行
等待 ms 后,怎么通知主线程呢?向任务队列 task queues 中加入任务
task queues 任务队列
事件环取一个任务执行,走一圈,再取一个任务执行,再走一圈......
这看起来很简单,但是如果考虑渲染时,就开始变得复杂了。
那该如何处理渲染呢? 渲染在另一边,
渲染包括,构建DOM tree,CSS OM,构建 RENDER Tree,布局 LAYOUT,绘制 PAINT ,
如何我们一直再左侧这边执行js代码,例如写了一个 while 死循环,那就永远到不了右侧的渲染环节,
页面的更新、交互就无效了。视频里有很生动的讲解。
当然 死循环 的写法也有影响,如果使用 setTimeout的话:
function loop(){
setTimeout(loop,0);
}
loop();
那页面的更新和交互并不会被阻塞。
为什么呢?👇
执行完任务队列中的一个任务之后,需要循环一圈再执行下一个任务,
当事件环到达右侧时,浏览器说:“我需要更新,ok不”,事件环:“yes”,可以进行渲染,
再看微任务,micro task,
关于它最初的目的:视频讲的有点迷(跳过~) 最初的目的:以一个事件来代表所有的变化, 例如for循环中插入100个span元素,以前会产生100个插入事件 ,而我们只想产生一个事件。
还是来看看 promise 吧 jrm,
micro task 也有一个队列,在js执行栈为空时执行,这句话很关键,没搞清的话后面会被面试拷打😶🌫️
微任务队列的执行和task queues 不同,微任务一直执行直到队列为空,期间产生新的微任务继续加入队列。
图解三个队列:
tasks 队列执行完一个之后就暂停执行,animation 队列 和 microtasks 队列 会不断执行直到队列为空。
面试拷打
微任务的调用时机
同步代码会放在执行栈上执行,当栈为空时再执行微任务,
情况一、用户点击按钮时,
首先第一个回调函数入栈,输出 L 1 ,加入微任务1,出栈
栈空,执行微任务1,输出 M 1
然后第二个回调函数入栈,输出 L 2 ,加入微任务2,出栈
栈空,执行微任务2,输出 M 2
情况二、使用 button.click() js代码调用
执行button.click(),
第一个回调函数入栈,....,出栈,click 还没有返回,栈并不为空,
第二个回调函数入栈,....,出栈,click 返回,再执行微任务 1 2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>event loop</title>
</head>
<body>
<button>你好</button>
<script>
const button = document.querySelector("button");
button.addEventListener("click", () => {
Promise.resolve().then(() => console.log("Micro 1"));
console.log("Listener 1");
});
button.addEventListener("click", () => {
Promise.resolve().then(() => console.log("Micro 2"));
console.log("Listener 2");
});
// 看情况!!
// 如果是用户点击
// 输出顺序是 L1 M1 L2 M2 !!!1
// 如果是使用 js 调用 click,则大有不同!
// 输出顺序时
// button.click();
</script>
</body>
</html>
script 阻塞DOM构建渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>script解析</title>
<script>
setTimeout(() => {
console.log("setTimeout");
console.log(document.body);
}, 0);
Promise.resolve().then(() => {
console.log("promise");
console.log(document.body);
});
console.log(document.body);
</script>
</head>
<body>
<h1>nihao</h1>
</body>
</html>
下载 html,
开始解析,构建 DOM,渲染
遇到了 script 标签,向任务队列中添加一个任务,
遇到setTimeout,加入队尾;
遇到promise,加入微任务队列;
同步代码放在执行栈上,执行 console.log, 此时body 为null,
执行栈为空时,执行微任务,console.log 打印 promise,此时body仍然为null,
这样第一个任务就执行完了,这时候可以进行页面渲染,body已经渲染完了,
到下一个循环,执行下一个任务,console.log 打印 setTimeout,此时body有值。
所以最后的结果是:
null
promise
null
setTimeout
<body></body>
再次升级,两个script,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>script解析</title>
<script>
setTimeout(() => {
console.log("setTimeout"); // 六、<body>
console.log(document.body);
}, 0);
Promise.resolve().then(() => {
console.log("promise"); // 二、null
console.log(document.body);
});
document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM 完全加载和解析"); // 五、DOMContentLoaded
});
console.log(1, document.body); // 一、null
</script>
<script>
setTimeout(() => {
console.log("setTimeout2"); // 七、<body>
console.log(document.body);
}, 0);
Promise.resolve().then(() => {
console.log("promise2"); // 四、null
console.log(document.body);
});
console.log(2, document.body); // 三、null
</script>
</head>
<body>
<h1>nihao</h1>
</body>
</html>