all articles

About JS Event Loop

2019-07-29 @sunderls

js





最近试试水面试了以下微信的前端岗位,不出意外要考算法,所以必挂。

除了算法之外,也问了一些其他的基础问题,发现自己研究的还是太少了,比如js的event loop。

题目

这是网上其他的公司的题目,只是用作例子

async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start')
setTimeout(function(){
    console.log('setTimeout') 
},0)  

async1();

new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})

console.log('script end')

分析

1. 首先,浏览器的组成

  1. UI Interface,UI组建,包括窗口啊,地址栏啥的
  2. Browser Engine, 用来bridge UI interface和渲染引擎的
  3. Rendienring Engine, 渲染html,包括html 和css等引擎
  4. JS engine: 执行js的环境
  5. networking, data 存储等

这个还比较好理解,记住就行了

2. JS Engine/ JS Runtime

我们写的JS代码会操作比如history,dom等内容,这一些都不是JS engine的范畴,实际上是浏览器给JS提供的额外方法。

另外setTimeout等也是浏览器提供的web api之一。

所以JS Runtime包含了以下部分

  1. JS Engine
  2. web apis

JS Engine只管执行代码。web apis提供浏览器的api

JS Engine包括三部分

  1. heap -> 存数据
  2. call stack -> 调用栈, 存call frame
  3. queue -> task 队列,一个call stack被清空了过后回去执行下一个task

这个还算好理解: ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

queue里面存的是事件和对应的js function。JS Runtime就不停的做如下loop

  1. queue里面取出一个message
  2. 执行这个message的function,这个function的后续调用会压入stack。直到stack中的function全部执行完毕
  3. 处理下一个message。

3. Task和micro tasks

实际上还有一个message queue 用来管理一些介于步骤2和3之间的task,叫做micro task。

产生micro task的有MutationObserver和promise.then.

setTimeout处理的是task。

4. 回归问题

初始化

callStack []
microtasks []
tasks [run script]

1.

console.log('script start')

执行,所以 script start被打印,然后被pop

callStack []
microtasks []
tasks [run script]

2.

setTimeout(function(){
    console.log('setTimeout') 
},0)  

这个会schedule一个新的task到最后。所以

callStack []
microtasks []
tasks [run script, setTimeout callback]

3.

callStack [async1]
microtasks []
tasks [run script, setTimeout callback]

async1的body如下

console.log('async1 start')
await async2()
console.log('async1 end')

显然 async1 start 被打印。

await async2() 就是promise的then 的callback, 所以

callStack [async1, async2]
microtasks []
tasks [run script, setTimeout callback]

执行async2,async2被打印. 但是async2啥也没干,所以callback then直接加入micro。async1结束,压入new Promise

callStack [Promise]
microtasks [async2.then]
tasks [run script, setTimeout callback]

4.

new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})
callStack [Promise, console.log]
microtasks [async2.then]
tasks [run script, setTimeout callback]

promise的构建方法是同步的所以 console.log被调用,promise1被打印

callStack [Promise, resolve]
microtasks [async2.then]
tasks [run script, setTimeout callback]

然后resolve被调用,添加一个then到micro,返回pop。插入下一个call Frame

callStack [console.log]
microtasks [async2.then, Promise.then]
tasks [run script, setTimeout callback]

script ends被打印,call Frame结束

callStack []
microtasks [async2.then, Promise.then]
tasks [setTimeout callback]

callStack为空, 查看microtasks 顺序执行所以async1 endpromise2被打印

callStack []
microtasks []
tasks [setTimeout callback]

最后setTimeout的callback被执行,所以 setTimeout被打印

5. micro中的resolve如果是async的呢?

async function async2(){
    return new Promise((resolve, reject) {
    setTimeout(() => {
console.log('async2');
resolve();
    }, 0);
})
}

如果是这样的话,async2 的then的添加会在new Promise之后,并且会在setTimeout之后,所以是

script start
async1 start
promise1
script end
promise2
undefined
setTimeout
async2
async1 end

setTimeout是web api,也意味着setTimeout做的只是把message加载task队列中,具体执行的时候需要看前面排队的情况,无法保证时间准确。



如果觉得有帮助到你的话,
欢迎支付宝donate