Comments (2)
EventLoop实践
我们用工具去测量一下,实际运行时是什么情况。
准备工作
- 目标:测试
setTimeout
、Promise.then
为代表的异步任务对渲染和计算的影响 - 对比:直接调用渲染、
setTimeout
渲染、Promise.then
渲染、组合渲染(宏任务和微任务交叉) - 工具:
Chrome Devtools Performance
开始测试
1. 直接渲染
<body>
<div id="message"></div>
<script>
let msg = document.querySelector('#message');
function appendMsg() {
let p = document.createElement('p');
p.innerHTML = Math.random().toString(16).substring(2);
msg.appendChild(p);
}
function main() {
// 同步渲染
for(let i = 0; i < 10; i++) {
appendMsg();
}
}
main();
</script>
</body>
直接循环操作dom
,给元素添加子元素。
渲染结果如下
后面的图,不再重复标注图例
我们得到这些结论:
- 在解析脚本的过程中发现了我们这个
script
,这个script
被同步解析,并添加到任务队列,属于第一个任务队列 - 解析脚本分为两段:编译脚本和执行脚本,
chrome
把这两步合称为评估脚本 main
函数里面的循环,所有操作都是同步执行的,所有操作执行完毕才返回- 执行宏任务完成后,评估脚本阶段结束,才进入
render
过程,因此一个任务队列的最后阶段才是render
(深紫色段) render
过程也分为两:重新计算样式和布局。- 第一个任务队列完毕后还有一些浏览器自身的任务队列进入调度,中间有间隔时间
上述结论如果是通用的,下面的结论也不再重复提示
2. 使用setTimeout
<body>
<div id="message"></div>
<script>
let msg = document.querySelector('#message');
function appendMsg() {
let p = document.createElement('p');
p.innerHTML = Math.random().toString(16).substring(2);
msg.appendChild(p);
}
function main() {
// 同步添加任务渲染,异步定时器触发
for(let i = 0; i < 10; i++) {
setTimeout(() => {
appendMsg();
}, 0)
}
}
main();
</script>
</body>
在循环中不断设置定时器,往任务队列里面添加任务,每个任务的内容为操作dom
执行appendChild
结果如下
我们可以得到这些结论
setTimeout
时间设置为0
,并不是立即触发任务,而是定时器在下一个任务队列调度时立即触发任务添加到任务队列的意思。- 循环中不断异步添加的任务,会生成对应多个任务队列,这些任务队列的间隔时间比较短。
- 每个任务队列里面的任务仍然是同步运行的
- 由于十个任务队列调度的间隔时间比较短,来不及渲染,因此这些
appendChild
操作被合并到最后一个任务队列里面执行了,该任务队列就只有render
dom
操作只是提交了样式变更,具体渲染还是在render
过程。整个过程还是同步的,只是因为render
可以合并,在两个任务队列做完同一个方法的操作,看起来是异步的。
3. 使用Promise.then
<body>
<div id="message"></div>
<script>
let msg = document.querySelector('#message');
function appendMsg() {
let p = document.createElement('p');
p.innerHTML = Math.random().toString(16).substring(2);
msg.appendChild(p);
}
function main() {
// 同步添加微任务渲染,当前任务结束取出执行
for(let i = 0; i < 10; i++) {
Promise.resolve().then(() => appendMsg())
}
}
main();
</script>
</body>
上述代码执行了同步的的微任务添加,我们要关注微任务什么时候被调度。
结果如下
这个就很明显阐释了微任务的调度和消费时间,我们可以得到这些结论
- 宏任务执行过程中添加微任务,这些微任务都会在本次任务队列的中间被调度
- 微任务的执行时机是在宏任务调度完之后,
render
执行前 - 几十个微任务排队被消费,这个消费过程,仍然是同步的
- 每一个
dom
操作都有独立的蓝色竖条,表明dom操作确实是消耗比较高的。而且dom
操作仍然是同步的 - 注意那个
DCL
的位置,由于我们的微任务都是同步调度和消费的,整个DCL
时间是等到所有任务结束之后的;而上一节我们用setTimeout
由于生成了新的任务队列,DCL
是更早的,具体的dom
操作是在DCL
之后的。 DCL
:DocumentContentLoaded
,文档加载完成,这时会触发window.onload
4. 交叉使用setTimeout
和Promise.then
<body>
<div id="message"></div>
<script>
let msg = document.querySelector('#message');
function appendMsg() {
let p = document.createElement('p');
p.innerHTML = Math.random().toString(16).substring(2);
msg.appendChild(p);
}
function main() {
for(let i = 0; i < 10; i++) {
setTimeout(() => {
Promise.resolve().then(() => appendMsg())
Promise.resolve().then(() => console.log(i));
}, 0);
}
}
main();
</script>
</body>
上述代码在同步调用中生成十个定时器,每个定时器内部添加两个微任务
结果如下:
结论如下:
- 每个任务队列生成的微任务都会在本次队列结束前被检查和消费
- 合并的
render
任务可能在中间调用,任务很多的情况下,UI就是渐进的,看起来不会掉帧,这是render
的策略(渲染器)
5. 其他情况
深层嵌套模拟
<body>
<div id="message"></div>
<script>
let msg = document.querySelector('#message');
function appendMsg() {
let p = document.createElement('p');
p.innerHTML = Math.random().toString(16).substring(2);
msg.appendChild(p);
}
function main() {
for(let i = 0; i < 10; i++) {
setTimeout(() => {
Promise.resolve().then(() => appendMsg())
Promise.resolve().then(() => {
setTimeout(() => {
console.log(i);
}, 0);
});
}, 50 * i);
}
}
main();
</script>
</body>
把setTimeout
时间间隔拉大,会得到一个更稀疏的任务队列调度图。
一些感悟
之前写代码很喜欢把异步写到Promise.then
里面,总觉得它实现了异步,又能够比setTimeout
的回调函数先执行,页面性能会蹭蹭往上涨。这两天写完EventLoop
,完全改变了我的想法:你在一个宏任务里面写一百个Promise.then
,它在事件循环里面仍然是同步调用的!这种卡起来你连原因也找不到。
因此,总结一下
- 微任务只是把一段代码放到了本次事件循环的末尾,而不会缩短一次事件循环的总时间
- 真正起作用的是宏任务们,各种原生提供的回调函数,撑起了
web
性能优化的半边天。
说到这里,又想起了最近研究的React Fiber
技术,同样的,如果React Fiber
采用Promise.then
来实现调度的话,页面仍然会卡,React
团队巧妙地利用requestAnimationFrame
|requestIdleCallback
等生成宏任务的方法,充分榨干了浏览器提供的能力,才实现了性能的飞跃~
不过要注意,我们这里套路的是同步代码的情况(比如Promise.then(() => console.log(1))
,回调中用到的仍然是同步代码,才造成了阻塞),一般我们的Promise
会搭配IO
、回调等特性使用,性能也是可以的,而且写法很优雅。不过要清楚的是,真正生效的是那些回调函数生成的宏任务。
from basic-programming-knowledge.
第三章-加深理解
原生鼠标事件顺序解读
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log("Promise 1"));
console.log("Task 1")
});
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log("Promise 2"));
console.log("Task 2")
});
// Task 1
// Promise 1
// Task 2
// Promise 2
脚本触发事件解读
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log("Promise 1"));
console.log("Task 1")
});
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log("Promise 2"));
console.log("Task 2")
});
button.click();
console.log("script");
// script
// Task 1
// Task 2
// Promise 1
// Promise 2
总图
总结
- 篇首提到的两个不同结果,本质上是由加入队列时机决定的
- script可能往主线线程丢了几个任务,而这些任务先执行完才会在末尾执行他们生成的微任务
from basic-programming-knowledge.
Related Issues (20)
- 2021-11-25 图论
- 【VIM】编辑器相关整理
- 【Rust】资源汇总
- 【React】资源汇总
- 【tools】最有用的那些软件、站点-工具链
- 【英语】学习资源
- 【vim】文艺复兴·VIM使用指南·Day 1
- 【vim】文艺复兴·VIM使用指南·Day 2
- 【vim】文艺复兴·VIM使用指南·Day 3
- 【翻译】monio关于monad的解释
- 【函数式编程】函子 HOT 1
- 【异步】async/await比Promise好吗?
- 【杂文】述职报告的思考
- 【函数式编程】什么是lambda演算? HOT 1
- 【函数式编程】haskell语言-haskell趣学指南 HOT 5
- 【工程化】如何创建一个现代化的前端基础库
- wsl2开发环境设置排坑
- 前端库
- 【产品】构建一个产品需要哪些优秀的工具?
- 【docker】mac配置docker开发环境/用vscode远程编码/逐行解说
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from basic-programming-knowledge.