JavaScript中的microtask与task

JS的任务执行机制

由于 JavaScript 是单线程的,所以它只有一个Call Stack,使得 JavaScript 在执行时有一个非常重要的特性:run to complete,只要运行就直到完成。

由于是单线程,所以只能通过异步解决性能问题(否则,如果前面一个任务阻塞了,那么后续的任务都要等待,这种效果是无法接受的)。

js在执行代码时存在着两个比较重要的东西:执行栈任务队列,这两个东西都是用来存储任务的,区别在于:执行栈里面存着的都是同步任务,也就是要按顺序执行的任务;而任务队列中存着的是一些异步任务,这些异步任务一定要等到执行栈清空后才会执行(这句话很重要)。

关于任务队列,它还分成两种,一种叫作macrotask queue(姑且这么命名,因为严格来说规范中只有说task,并没有提到macrotask这个概念。这里为了容易区分,可以理解为macrotask=task!=microtask),另一种叫作microtask queue

如果同时考虑node环境和浏览器环境的话,这两种任务分别对应以下api

microtasks:

  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver

macrotasks:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染
  • script标签中的整体代码

看下面的例子

(function test() {
    setTimeout(function() {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() {
        console.log(5);
    });
    console.log(3);
})()

//  输出结果:1,2,3,5,4

从输出结果可以推出的:

  • Promise.then是异步执行的,而创建Promise实例(executor)是同步执行的。
  • setTimeout的异步和Promise.then的异步看起来 “不太一样” ——至少是不在同一个队列中。

因为Promise 属于一个microtaskEventloop在执行完堆栈,或者一个task后,会优先询问microtask queue,如果队列中有任务要执行,则执行,一直到队列为空,然后再执行下一个task(每一个microtasktask都遵循run to complete规则)。

Event Loop(事件循环)是怎么来处理task和microtask的

  • 每个线程有自己的事件循环,所以每个web worker有自己的,所以它才可以独立执行。然而,所有同属一个originwindows共享一个事件循环,所以它们可以同步交流。
  • 事件循环不间断在跑,执行任何进入队列的task。
  • 一个事件循环可以有多个task source,每个task source保证自己的任务列表的执行顺序,但由浏览器在(事件循环的)每轮中挑选某个task sourcetask
  • tasks are scheduled,所以浏览器可以从内部到JS/DOM,保证动作按序发生。在tasks之间,浏览器可能会render updates。从鼠标点击到事件回调需要schedule task,解析htmlsetTimeout这些都需要。
  • microtasks are scheduled,经常是为需要直接在当前脚本执行完后立即发生的事,比如async某些动作但不必承担新开task的弊端。microtask queue在回调之后执行,只要没有其它JS在执行中,并且在每个task的结尾。microtask中添加的microtask也被添加到microtask queue的末尾并处理。
  • 一个事件循环过程中,只执行一个macrotask,但是可能执行多个microtask
  • 执行栈中的任务产生的microtask会在当前事件循环内执行
  • 执行栈中的任务产生的macrotask要在下一次事件循环才会执行

为什么优先执行microtask?
有时候task太重了,每一个task结束后,都会重新渲染页面。microtasktask拥有更高的优先级,可以做一些比较有意思的事情。

举例题目的执行流程如下:

  1. 当前task运行,执行代码。首先setTimeoutcallback被添加到tasks queue中;
  2. 实例化promise,输出 1; promise resolved;输出 2;
    3.promise.thencallback被添加到microtasks queue中;
  3. 输出3;
  4. 已到当前taskend,执行microtasks,输出 5;
  5. 执行下一个task,输出4。

你可能感兴趣的:(前端开发,JavaScript,web前端)