js事件循环

115 阅读3分钟

Javascript 运行机制

JavaScript 语言特点是单线程,只有一个调用栈,一次只能执行一件事。在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。因此JS是一个非阻塞异步并发式的编程语言。

异步操作有哪些?

  • setTimeout、setInterval定时器
  • 事件绑定
  • promise
  • async await
  • 读写文件

当遇到异步任务时,会将异步任务放到任务队列中,等到整个运行栈中的内容执行完后再去执行任务队列中的内容。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

主线程和任务队列的示意图如下:

2.png

任务队列

微任务包括 process.nextTick ,promise ,MutationObserver。 宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

任务队列 中存放的异步任务会被分为了两大类:微任务宏任务微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task

  • 微任务: process.nextTick ,promise ,MutationObserver
  • 宏任务: script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering
  1. JS的执行顺序,先同步后异步
  2. 异步中任务队列的执行顺序: 先微任务microtask队列,再宏任务macrotask队列
  3. 调用Promise 中的resolve,reject属于微任务队列,setTimeout属于宏任务队列 注意以上都是 队列先进先出

示例如下:无论怎么调整顺序,输出结果都是:1 4 3 2

console.log(1);
setTimeout(() => {
    console.log(2);
  }, 0);
new Promise((resolve) => {
  resolve();
}).then(() => {
  console.log(3);
})
console.log(4);

微任务 在 宏任务 之前执行,即使 微任务 在 宏任务 之后才被加入到任务队列中。

执行顺序为: 微任务 > DOM渲染 > 宏任务

Event Loop

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

3.png

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

执行栈中的代码(同步任务),总是在读取"任务队列"(异步任务)之前执行

知乎解释: 为了插队, 一个 Event LoopMicrotask 是在 Macrotask 之后调用,Microtask 会在下一个 Event Loop 之前执行调用完,并且其中会将 Microtask 执行当中新注册的 Microtask 一并调用执行完,然后才开始下一次 Event loop,所以如果有新的 Macrotask 就需要一直等待,等到上一个 Event loop 当中 Microtask 被清空为止。 由此可见, 我们可以在下一次 Event loop 之前进行插队。如果不区分 Microtask Macrotask,那就无法在下一次 Event loop 之前进行插队,其中新注册的任务得等到下一个 Macrotask 完成之后才能进行,这中间可能你需要的状态就无法在下一个 Macrotask 中得到同步。

参考:www.ruanyifeng.com/blog/2014/1…