Event loop

2021.09.30

https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

JavaScript 是单线程的,一般发送的网络请求是异步的吧,但是网络请求也不会阻塞页面渲染,这又是怎么回事呢?

对 eventloop 就是为了解决异步编程而出现的模型。JavaScript 的运行时包含3个概念,

  1. stack

    一个 stack 由一堆 frames 组成,一个函数的调用会在 stack 上创建一个 frame,一个函数的 return 会销毁调用时创建的 frame。当所有的 frame 都被销毁的时候,stack 就空了。

  2. heap

    JS 对象就被分配在这一块内存区。

  3. queue(macro task queue & micro task queue)

    由一堆待处理的异步消息(task/message)组成,而消息其实分为两种,宏任务和微任务,对应的也有两个 queue,micro task queue 和 macro task queue。setTimeout,event listener 等会作为宏任务被 enqueue 到这个 macro task queue 去, promise 会作为微任务被 enqueue 到 micro task queue。

    当 stack 为空的时候,event loop 会先从 macro task queue 里 dequeue 一个消息,并调用,调用会在 stack 上生成一堆 frame,等待 stack 又变空的时候,event loop 会在 dequeue 下一个 message 之前,先会清空 microtask queue(dequeue 一个,到 stack 上执行一个,等 stack 为空再去 check micro task queue,有的话再 dequeue,执行,直到 micro task queue 被清空),清空后 event loop 才会继续 dequeue 下一个 macrotask queue,依次往复)

    题外话:宏任务在 ecma 规范里面叫做 ScriptJobs,微任务叫做 PromiseJobs

https://www.educative.io/edpresso/what-is-an-event-loop-in-javascript
https://www.educative.io/edpresso/what-is-an-event-loop-in-javascript

参考这个例子写了个 demo,这里不会涉及到过多的 promise 相关的知识点,你只需知道 promise resolve 的时候,.then 注册的回调函数会作为微任务被 enqueue 到 micro task queue 去,

function foo() {
    console.log('foo');
    new Promise(
        function(resolve, reject) {
            setTimeout(function() {
                resolve('RESOLVING');
            }, 5000);
        }
    )
    .then(
        function(value) {
            console.log(value);
        }
    )
}
foo();

上面 foo 被调用的时候会发生什么?

所以最终打印出来的应该是,

foo
RESOLVING // 大约五秒后

总结:其实微任务和宏任务解释起来也比较简单,微任务就是优先级比较高的宏任务,需要在 stack 为空的时候,但是又在下一个宏任务前需要执行的任务。


学习的时候遇到的一个坑,之前看 MDN 上的 Microtask 文档的时候,发现有一个例子好像不对,dispatch 了一个 event 后,不是加到 ScriptJobs 队列里去的吗?然后自己还写了个 demo,不过最初 demo 写的有问题所以让自己愈发觉得 MDN 上文档这部分写的有问题,于是提了个 bug 给 MDN,过了两个小时,觉得可能还是自己的问题,那么多人看怎么就自己觉得有问题,是不是自己的 demo 写的有问题,觉果发现还真的是,那么我理解上还是有问题,于是自己看了看,发现 dispatchEvent 这个方法竟然是同步执行的。。。 然后立马 close 了那个 issue (汗

这个应该是错的 我本来想 但发现自己错了...
这个应该是错的 我本来想 但发现自己错了...

其他

关于为什么要使用微任务呢,官方的解释是,

  1. Ensuring ordering on conditional use of promises
  2. Batching operations

参考

  1. Microtask guide - MDN
  2. fetch Polyfill use XHR - github
  3. stack, frame - MDN
  4. javascript promises, the event loop, and the job queue - stackoverflow
  5. sec-jobs-and-job-queues - ecma
  6. new operator
  7. Jobs and Host Operations to Enqueue Jobs - ecma
  8. XMLHttpRequest
  9. dispatchEvent
  10. Concurrency model and the event loop
  11. callStack - MDN