关于浏览器的js event-loop(事件轮训)

461 阅读3分钟

1. 什么是浏览器的event-loop

event-loop是指事件轮询机制。因为js是一个单线程语言,它的执行机制是这样的:

  1.  所有的同步任务都在主线程上执行,形成一个执行栈
    
  2. 主线程之外有一个任务队列(task queue),当遇到异步任务时,主线程不会等待异步的完 成,而是继续往下走,异步任务有了结果之后,就会往任务队列中插入一个事件,
    
  3. 当主线程上的同步任务全部执行完,就会询问任务队列是否有已经完成的事件,如果有,该事件就会进入执行栈,开始执行。
    
  4. 不断重复上面的第三步。
    

总结:因为只有一个线程,所以必须等主线程上的任务全部执行完毕,才会去执行任务队列中已完成的事件,不断重复这个过程,便是event-loop

2.宏任务,微任务

在任务队列中的异步任务有两种类型,两种任务有不同的执行时机。

宏任务(macro task):setTimeout、setInterval、setImediate、I/O、UI rendering 微任务(micro task):process.nextTick、promise、Object.observe、mutationObserve

在上面event-loop的第三步中,主线程在执行全部同步任务后,会进入任务队列中执行,执行的详细过程是这样的。

1、主线程同步任务全部清空,将当前全部微任务加入执行栈并清空。

2、如果在执行微任务的过程同时产生了微任务,就会将该任务加入当前的微任务队列末尾,并在当前微任务全部结束后执行。

3、清空加入了执行的全部微任务之后就会取出加入宏任务队首的第一个任务放入执行栈末尾并执行。

4、执行栈的宏任务执行结束,就会查看微任务队列并执行。

5、重复2-4步。。。

3、练习题

题目一:

console.log(1);  

setTimeout(() => { //setTimeout1
  console.log(2);
  Promise.resolve().then(() => { //resolve2
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5) //resolve1
}).then((data) => {
  console.log(data);
})

setTimeout(() => { //setTimeout2
  console.log(6);
})

console.log(7);

输出结果: 1 4 7 5 2 3 6

解析: 1、执行console.log(1)

2、遇到setTimeOut,属于异步宏任务,将放入任务队列中。当前 macro task[ setTimeout1 ]

3、遇到promise,执行console.log(4);,promise的resolve()部分类似于callback,属于异步微任务,放入微任务队列,micro task [ resolve1 ]

4、遇到setTimeOut,异步任务,将它放入异步宏任务队列中,当前 macro task[ setTimeout1 , setTimeout2]

5、遇到console,直接执行。

6、同步任务执行完毕,查看微任务队列,目前微任务仅有一个promise的resolve,所以将它加入执行栈,打印出5 micro task被清空

7、微任务清空,接下来是第一个任务队列的第一个宏任务。也就是上面第二步的setTimeout,打印出2。 macro task[ setTimeout2 ]

8、遇到resolve,属于微任务,将其加入微任务队列,micro task[ resolve2 ] 当前宏任务打印结束之后,清空微任务,故打印出3 ,micro task被清空

9、微任务清空,查看下一个宏任务,即setTimeout,打印出6 ,macro task被清空,执行结束。

第二题:

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
  
  Promise.resolve().then(() => {
    console.log(6)
  }).then(() => {
        console.log(7)
        setTimeout(() => {
           console.log(8)
    }, 0);
  });
})

setTimeout(() => {
  console.log(9);
})

console.log(10);

打印结果 1 4 10 5 6 7 2 3 9 8

总而言之,过程基本如下: 同步任务->微任务清空->第一个宏任务->微任务清空->第二个宏任务....

4、总结

浏览器的event-loop就是在不断清空执行栈的过程。上文提到的执行队列任务时并没有提到UI rendering这个任务,根据html standard,在每一个宏任务之后,然后清空微任务,接着就会执行UI rendering事件.接下来进入第二轮循环。这就是整个浏览器的event-loop