咱们先来看个实际中的场景:javascript
在浏览器上打印出1~100万。java
对于这个需求,咱们第一个反应的代码是这样的:浏览器
for (let i = 0; i < 100 * 10000; i++) {
const div = document.createElement("div");
div.innerText = i;
document.body.append(div);
}
复制代码
看着好像没问题,固然这里有的同窗会说,你这里操做了100万次dom,性能损耗很严重。这是一个问题,可是这个不是本文讨论的重点。markdown
咱们实际运行一下这段代码,发现一开始的时间(很短)会没法正常作交互,好比鼠标悬浮,选中文字等操做。那么为何会形成这个现象呢?不急,咱们一步步分析。app
以Chrome为例,一个标签页独占一个渲染进程,而JavaScript解释线程是属于这个渲染进程内的。dom
咱们先聊下前置知识,JavaScript执行和屏幕渲染是互斥的,缘由是屏幕渲染须要根据DOM结构,而JavaScript是有能力修改DOM的,因此会在JavaScript执行完毕后,执行渲染动做。通常来讲,屏幕的渲染是60HZ,差很少16ms须要切换一张图片,人眼才不会意识到卡顿。异步
那么咱们再回来看刚刚的代码async
for (let i = 0; i < 100 * 10000; i++) {
}
复制代码
结论很明显了,这句代码的执行明显超过了16ms!!!oop
事件循环机制(Event Loop)就是为了解决这个问题而提出的。性能
简单来讲,咱们把大任务切成了多个小任务,让这些任务的执行和渲染流程交错进行。
计算100万个(1s) => 屏幕渲染
计算10万个(0.1s) => 屏幕渲染 => 计算10万个(0.1s) => 屏幕渲染 => 计算10万个(0.1s) => ...
这里就不贴出具体的代码实现了,感兴趣的同窗能够本身动手写一下。
事件循环机制(Event Loop)出现后,JavaScript的执行任务分为了两类:同步任务和异步任务。而异步任务,又分红了两类:
总体代码
、定时器
、I/O
Promise
、MutationObserver
、observer
这里咱们须要注意一点,Promise自己是同步代码,可是它的回调then catch是异步
new Promise((res, rej) => {
res('ok') // 同步任务
}).then((result) => {
console.log(result) // 异步任务中的微任务
})
复制代码
其实咱们会好奇,为何是分红这两类?为何不是三类或者一类?其实这是一种权衡的策略。宏任务的定义为耗时长的任务,微任务为耗时短的任务。在实际执行中,须要执行的任务分红多,因此就会有优先级的问题,其实宏任务和微任务的制定就是一种折中,为了权衡执行时间和运行效率。
咱们刚刚提到了优先级的问题,那咱们就来展开聊聊。通常状况下,微任务的执行优于宏任务。为何是通常状况下呢?咱们来看下这段代码:
for(let i = 0; i < 10; i++) {
setTimeout(() => {
console.log('宏任务开始')
for (let j = 0; j < 10; j++) {
microtask();
}
})
}
function microtask() {
return new Promise(async (res) => {
console.log('微任务开始')
await microtask();
res();
})
}
复制代码
注册10个宏任务 => 运行第1个宏任务 => 注册10个微任务 => 微任务执行过程当中又注册了微任务 => 微任务执行过程当中又注册了微任务 => ...
这是一个微任务的死循环,若是按微任务的执行优于宏任务
的论据,宏任务是不可能触发第二个的,由于微任务队列一直在被push。
可是实际状况下,咱们是能够看到第二个宏任务开始的。这是为何呢?实际上是Chrome V8作的一个小策略,在微任务过多的时候,会执行下一个宏任务。
咱们经过了开头的一个小例子,引入了事件循环机制(Event Loop),而执行任务分为了两类:同步任务和异步任务。而异步任务,又分红了两类:宏任务和微任务。通常状况下,微任务的执行优于宏任务。当在极端场景下,好比微任务实在是太多了,Chrome V8会先执行宏任务。
最后打波小广告,美团校招社招内推,不限部门,不限岗位,不限投递数量,海量hc,快来快来~