js的事件循环和任务队列

js 异步、栈、事件循环、任务队列

在开发中经常遇到js的异步问题,为了方便理解,记录下来,随时回顾。

  • 以下的所有代码都是在浏览器环境下运行

在浏览器中js的运行是依赖浏览器js引擎来解析的,并且是在一定的runtime(运行时)的环境被调用,被执行。由于js引擎是单线程的,所以在执行dom渲染,script引入的时候这些操作是同步的,js引擎会通过 Event Loop 的机制,按顺序把任务放入栈中执行

而在代码中产生的异步代码则是由 runtime 提供的,拥有和Js引擎互不干扰的线程

栈是一个后进先出的一种数据结构,执行起来效率比较高,往往堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针,在函数调用的时候,会产生函数的执行栈,也叫执行上下文,这个执行环境中存在着这个函数的私有作用域,上层作用域的指向,函数的参数,这个作用域中定义的变量以及这个作用域的this对象。 而当一系列函数被依次调用的时候,因为js是单线程的,同一时间只能执行一个函数,于是这些函数被排队在一个单独的地方。这个地方被称为执行栈。

当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码全部执行完毕。

总的来说

  • 栈存放着一些基础类型变量以及对象的指针
  • 当代码执行的时候,同步代码按照执行顺序开始执行
  • 当代码执行的时候,碰到函数,引擎会在栈里产生这个函数执行栈,也叫执行上下文。
  • 当代码执行到函数的时候,会进入这个执行环境继续执行其中的代码,反复进行,全部执行完

任务队列

Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个
  • 宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • 微任务:process.nextTick (node.js中进程相关的对象), Promise, Object.observer, MutationObserver。

在浏览器的 Event Loop机制中,整个流程可以用张图来表示一下:

avatar

这张图中可以看到:

  • 微任务队列(micro tasks)只会有一个
  • 宏任务队列(macro tasks)可以有多个
  • click ajax 等回调方法都会进入到宏任务队列(macro tasks)中,当然也包括上面的

而在浏览器的Event Loop机制运行时,宏任务队列(macro tasks)和微任务队列(micro tasks)的关系,关于这点详见(关系

  • 宏任务按顺序执行,且浏览器在每个宏任务之间渲染页面
  • 所有微任务也按顺序执行,且在以下场景会立即执行所有微任务
    • 每个回调之后且js执行栈中为空。
    • 每个宏任务结束后。

而在 NodeJs 的 Event Loop 遵循的是 libuv

这个库是node作者自己写的,内部实现了一整套的异步io机制(内部使用c++和js实现),使我们开发异步程序变得简单,因为这个原因导致了一些js解析和浏览器的会不一样。

NodeJs 的运行是这样的:

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

当每个阶段执行完毕后,都会执行所有微任务(先 nextTick,后其它),然后再进入下一个阶段。

最后我们来段代码彻底解析下两类任务队列在运行时的逻辑

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

结合上面讲的逻辑 可以分析一波得出最后答案是1,7,6,8,2,4,3,5,9,11,10,12

posted @ 2019-03-28 15:57  十点过六分  阅读(3300)  评论(0编辑  收藏  举报