jsJS

理解事件循环与任务队列

2018-01-08  本文已影响116人  Hyelim

JS是单线程的

JS是单线程的,也就是它一次只能执行一段代码。JS中其实是没有线程概念的,所谓的单线程也只是相对于多线程而言。JS的设计初衷就没有考虑这些,针对JS这种不具备并行任务处理的特性,我们称之为“单线程”。

虽然JS运行在浏览器中是单线程的,但是浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中。浏览器中很多异步行为都是由浏览器新开一个线程去完成,一个浏览器至少实现三个常驻线程:

JS引擎

JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中,比如最出名的就是Chrome浏览器的V8引擎,如下图所示,JS引擎主要有两个组件构成:

调用栈

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

事件循环与任务队列

事件循环可以简单描述为:

  1. 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;
  2. 在此期间WebAPIs完成这个事件,把回调函数放入CallbackQueue中等待;
  3. 当执行栈为空时,Event Loop把Callback Queue中的一个任务放入Stack中,回到第1步。
var start=new Date();
setTimeout(function cb(){
    console.log("时间间隔:",new Date()-start+'ms');
},500);
while(new Date()-start<1000){};
  1. main()入栈,局部变量start初始化;
  2. setTimeout入栈,出栈,丢给WebAPIs,开始定时500ms;
  3. while循环入栈,开始阻塞1000ms;
  4. 500ms过后,WebAPIs把cb()放入任务队列,此时while循环还在栈中,cb()等待;
  5. 又过了500ms,while循环执行完毕从栈中弹出,main()弹出,此时栈为空,Event Loop,cb()进入栈,log()进栈,输出'时间间隔:1003ms',出栈,cb()出栈

Microtasks和Macrotasks

macro-task(Task)包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task(Job)包括:process.nextTick, Promises.then(), Object.observe(已被废弃), MutationObserver

根据 WHATVG 的说明,在一个事件循环的周期(cycle)中一个 (macro)task 应该从 macrotask 队列开始执行。当这个 macrotask 结束后,所有的 microtasks 将在同一个 cycle 中执行。在 microtasks 执行时还可以加入更多的 microtask,然后一个一个的执行,直到 microtask 队列清空。

setTimeout(function cb() {
    console.log(4);
}, 0);
new Promise(function executor (resolve) {
    console.log(1);
    for(var i = 0; i < 10000; i++) {
      i == 9999 && resolve();
    }
    console.log(2);
}).then(function onFulfilled() {
    console.log(5);
});
console.log(3);
//执行结果:1 2 3 5 4

或者可以简单写成这样:

setTimeout();
var promise = new Promise(executor);
promise.then(callback);
console.log(3);
  1. main()入栈;
  2. setTimeout入栈,出栈,丢给WebAPIs,开始定时0ms(实际上不一定是多少,总之大于0),到时之后,将回调函数cb()放入macrotask queue;
  3. Promise构造函数executor()入栈,log(1)入栈,输出1,出栈;
  4. for循环入栈,当i=9999时,resolve()入栈,Promise实例的状态变为fulfilled(完成),resolve()出栈。构造函数执行完后,我们得到了promise(它是resolved);
  5. promise.then入栈,onFulfilled(then方法绑定的resolved状态的回调函数)放入microtask queue;
  6. log(2)入栈,输出2,出栈;
  7. executor()出栈;
  8. log(3)入栈,输出3,出栈,main()出栈;
  9. 此时栈为空,microtask queue中的任务可以进栈了,onFulfilled()入栈,log(5)入栈,输出5,出栈;
  10. 此时Stack和microtask queue都为空,Event Loop,将macrotask queue中的cb()入栈,log(4)入栈,输出4,log(4)出栈,cb()出栈

参考资料:

  1. Tasks, microtasks, queues and schedules . https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
  2. Understanding Javascript Function Executions — Call Stack, Event Loop , Tasks & more — Part 1 . https://medium.com/@gaurav.pandvia/understanding-javascript-function-executions-tasks-event-loop-call-stack-more-part-1-5683dea1f5ec
  3. How JavaScript works: an overview of the engine, the runtime, and the call stack . https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
  4. 理解事件循环一(浅析) . https://github.com/ccforward/cc/issues/47
  5. 理解事件循环二(macrotask和microtask . https://github.com/ccforward/cc/issues/48
  6. 从Promise来看JavaScript中的Event Loop、Tasks和Microtasks . https://github.com/creeperyang/blog/issues/21
  7. 理解 Node.js 事件循环 . http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html
  8. Philip Roberts: What the heck is the event loop anyway? . https://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html
  9. http://latentflip.com/loupe
  10. Promises/A+ . https://promisesaplus.com/
上一篇 下一篇

猜你喜欢

热点阅读