浏览器的event loop和node的event loop

1.什么是event loop

event loops也就是事件循环,它是为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking),用户代理(user agent)的工做而产生的一个机制。javascript

2.JavaScript的运行机制

2.1 单线程的JavaScript

JavaScript语言的一大特色就是单线程,也就是说在同一时间只作同一件事。这是基于js的执行环境决定的,由于在浏览器中,有许多的dom操做,若是在同一时间操做一个dom,很容易形成混乱,因此为了不发生同一时间操做同一dom的状况,js选择只用一个主线程执行代码,来保证程序执行的一致性,单线程的特色也应用到了node中。java

2.2 JavaScript中的任务和队列

JavaScript是单线程的,也就意味着全部任务须要排队,前一个任务执行完,才能执行下一个任务,可是由于IO设备(输入输出设备)很慢(好比Ajax从网络读取数据),不得不等待结果返回以后才能继续,这样的执行效率很慢。 因而分红了两种任务来处理,同步任务和异步任务。 同步任务是指在主线程排队的任务,只有前面的任务执行完以后才执行后面的任务。 异步任务指的是任务不进入主线程,而进入到一个任务队列(task queue),主线程的任务能够继续日后执行,而在任务队列里的异步任务执行完会通知主线程。node

3.浏览器的event loop

3.1执行栈与事件队列

当javascript代码执行的时候会将不一样的变量存于内存中的不一样位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。当全部全部同步任务都在主线程上执行时,这些任务被排列在一个单独的地方,造成一个执行栈promise

当浏览器js引擎解析这段代码时,会将同步任务顺序加入执行栈中依次执行,当遇到异步任务时并不会一直等待异步任务返回结果再执行后面的任务,而是将异步任务挂起,继续执行同步任务,当异步任务返回结果时,将异步任务的回调事件加入到一个事件队列(Task Queue)当中去,这个事件队列里的任务并不会当即执行,而是等同步任务所有执行完,再依次执行事件队列里的事件。浏览器

依次执行同步任务,完成后依次执行事件队列,完成后再去执行同步任务,这样造成了一个循环,就是事件循环(Event Loop)。bash

3.2宏任务(macro task)与微任务(micro task)

异步任务又分为宏任务与微任务两种,微任务并非老老实实的按照事件队列的顺序去执行,而是按照microTask—>macroTask的顺序去执行,先执行完队列中全部的microTask再去执行macroTask网络

宏任务和微任务的分类dom

  • MacroTask: script(总体代码), setTimeout, setInterval, setImmediate(node独有), I/O, UI rendering异步

  • MicroTask: process.nextTick(node独有), Promises, Object.observe(废弃), MutationObserversocket

举个例子
setTimeout(()=>{
    console.log(1)
})

Promise.resolve().then(function() {
    console.log(2)
})
console.log(3)

执行结果是:3 2 1
这是由于事件循环的顺序是:同步代码=>微任务=>宏任务
复制代码

4.node的event loop

  • timers: 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。

  • I/O callbacks: 这个阶段执行几乎全部的回调。可是不包括close事件,定时器和setImmediate()的回调。

  • idle, prepare: 这个阶段仅在内部使用,能够没必要理会。

  • poll: 等待新的I/O事件,node在一些特殊状况下会阻塞在这里。

  • check: setImmediate()的回调会在这个阶段执行。

  • close callbacks: 例如socket.on('close', ...)这种close事件的回调。

event loop的每一次循环都须要依次通过上述的阶段。 每一个阶段都有本身的callback队列,每当进入某个阶段,都会从所属的队列中取出callback来执行,当队列为空或者被执行callback的数量达到系统的最大数量时,进入下一阶段。这六个阶段都执行完毕称为一轮循环。

举个例子(1)
浏览器与Node执行顺序的区别
setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
})

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
})

复制代码

浏览器输出: time1 promise1 time2 promise2 由于promise是microtask,因此当第一个setTimeout执行完以后,先执行promise。

Node输出: time1 time2 promise1 promise2 由于time1和time2都在timers阶段,因此先执行timers,promise的回调被加入到了microtask队列,等到timers阶段执行完毕,在去执行microtask队列。

举个例子(2)
MicroTask队列与MacroTask队列
setTimeout(function () {
   console.log(1);
});
console.log(2);
process.nextTick(() => {
   console.log(3);
});
new Promise(function (resolve, rejected) {
   console.log(4);
   resolve()
}).then(res=>{
   console.log(5);
})
setImmediate(function () {
   console.log(6)
})
console.log('end');
复制代码

node输出的顺序是 2 4 end 3 5 1 6 首先执行的是同步任务中的2 4 end,而后是microTask队列中的process.nextTick:三、promise.then:5,最后是macroTask队列中的setTimeout:一、setImmediate:6,因为Timer优于Check阶段,因此先1后6。

相关文章
相关标签/搜索