Event Loop原理

纵然是瞬间 提交于 2020-01-16 05:56:51

前言

灵魂三问

  • JS为什么是单线程的?
JS最初被设计用在浏览器中,那么想象一下,如果浏览器中的JS是多线程的。
场景描述:
那么现在有2个进程,process1 process2,由于是多进程的JS,所以他们对同一个dom,同时进行操作
process1 删除了该dom,而process2 编辑了该dom,同时下达2个矛盾的命令,浏览器究竟该如何执行呢?
  • 为什么需要异步?
如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。
对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验
  • 单线程又是如何实现异步的呢?
既然JS是单线程的,只能在一条线程上执行,又是如何实现的异步呢? 答案就是**是通过的事件循环(event loop)**

event loop

javascript 是一门单线程的脚本语言,也就意味着同一个时间只能做一件事,但是单线程有一个问题:一旦这个线程被阻塞就无法继续工作了,这肯定是不行的。由于异步编程可以实现非阻塞的调用效果,引入异步编程自然就是顺理成章的事情了。

从同步异步来理解JS的执行机制

    console.log(1)
    setTimeout(function(){
        console.log(2)
    },0)
    console.log(3)
  • 首先判断JS是同步还是异步,同步就进入主进程,异步就进入event table
  • 异步任务在event table中注册函数,当满足触发条件后,被推入event queue
  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主进程中

宏任务&微任务

宏任务——微任务

Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)

**宏任务**:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
**微任务**:process.nextTick, Promise的then或catch, Object.observer, MutationObserver.

Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。

当stack空的时候,就会从任务队列中,取任务来执行。浏览器这边,共分3步:

  1. 取一个宏任务来执行。执行完毕后,下一步。
  2. 取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。
  3. 更新UI渲染,进入下一个loop。

只要执行栈中没有其他的js代码正在执行(即当前宏任务队列执行完成),微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会立即被执行。

举个例子来说明当然最好不过

console.log('script start');

// 微任务 1
Promise.resolve().then(() => {
    console.log('micro_task_1');
    // 微任务1 执行时加入微任务队列尾部
    Promise.resolve().then(() => {
            console.log('micro_task_1>micro_task_1');
    });
});

// 宏任务 1
setTimeout(() => {
    console.log('macro_task_1');
    Promise.resolve().then(() => {
        console.log('macro_task_1>micro_task_1');
        setTimeout(()=>{
            console.log('macro_task_1>micro_task_1>macro_task_1');
        })
    });
    Promise.resolve().then(() => {
        console.log('macro_task_1>micro_task_2');
    });
}, 0);

// 宏任务 2
setTimeout(() => {
    console.log('macro_task_2');
}, 0);

var s = new Date();
while(new Date() - s < 50); // 阻塞50ms
// 微任务 2
Promise.resolve().then(() => {
    console.log('micro_task_2');
});

// 宏任务 3
setTimeout(() => {
    console.log('macro_task_3');
}, 0);
console.log('script end');

// script start
// script end
// micro_task_1
// micro_task_2
// micro_task_1>micro_task_1

// macro_task_1
// macro_task_1>micro_task_1
// macro_task_1>micro_task_2

// macro_task_2

// macro_task_3

// macro_task_1>micro_task_1>macro_task_1

  • 先分析主任务script ,同步任务分别输出 script start>script end 异步任务中包含两个微任务,三个宏任务
  • 先执行微任务,执行第一个微任务micro_task_1 的时候又产生了一个新的微任务micro_task_1>micro_task_1,加入微任务队列尾部,再执行micro_task_2,直到微任务队列为空, 如果微任务中嵌套宏任务,则把它加入对应宏任务队列尾部。因此顺序为micro_task_1 > micro_task_2 > micro_task_1>micro_task_1
  • 进入下一个event-loop,先调用下UI render。 再取出下一个宏任务macro_task_1,以此类推。

event-loop
浏览器的 Event Loop 遵循的是 HTML5 标准,而 NodeJs 的 Event Loop 遵循的是 libuv

对比nodejs

  • 执行您的主代码。这里相同,遇到异步处理,就会分配给对应的队列。直到当前宏任务队列执行完毕。
  • 执行当前宏任务队列中出现的所有微任务:先执行完所有nextTick(),然后在执行其它所有微任务。

总结:

  • 每个macroTask队列中的macroTask按顺序执行,在每个macroTask之间渲染页面
  • 一个macroTask执行结束(即js执行栈中为空),会立即处理macroTask执行过程中产生的microTask并且按顺序执行。microTask产生的macroTask会自动加入相应的宏任务队列。
  • 每次loop会把这次宏任务产生的所有微任务执行完,在进行下一次loop。

附录

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!