模拟面试来解释事件循环

178 阅读4分钟

问题一:说说浏览器事件循环是什么?

JavaScript有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同

大家可能都对于事件循环(EventLoop)有一些自己的理解,但是我要说的是看完我这篇问题让你看看面试官在面试的时候有多么丧心病狂。这个问题一般的开场就是让你聊一下事件循环是怎么理解的,我觉得大家说出以下关键词就算是及格了。

事件循环关键词:任务队列(EventQueue),JS的单线程,异步编程的需求,宏任务,微任务

假设大家随便说了说自己对于事件循环的理解,那么下面我的新问题就来了。

问题二:为啥要有一个事件循环?

其实答案我们上面的关键词已经点出来了,因为JS是单线程的,为什么单线程,可以说一下因为DOM结构假如有两个线程同时操作DOM就会产生A线程删除了一个节点B线程却在使用这个节点,这样的问题就很难处理,虽然不是处理不了,但是JavaScript在设计之初就选择了单线程的方案。

那么单线程怎么了为啥要事件循环啊,因为我们也想要一种异步执行的机制一种并发机制。所以有了事件循环。

问题三:上面说了宏任务,微任务你能详细说说吗?

宏任务:JS整体代码,setTimeout,setInterval,IO操作 微任务:Promise.then(),MutaionObserver

问题四:为啥一种任务不行还需要微任务存在?

宏任务的队列是先进先出的机制,所以当你需要安排一个高优先级,需要插队的任务的时候就不行了。除了插队的作用,微任务和宏任务两种任务机制也能让浏览器对于事件循环进行一些优化等复杂操作,比如MutaionObserver是监听DOM的属性变化放在微任务里面就可以合并处理。

问题五:说一说宏任务和微任务的执行顺序

在浏览器中,每个事件循环遵循如下规则:

  • 先从宏任务队列里面取出一个宏任务加入执行栈并执行
  • 然后检查微任务队列,把微任务队列的所有任务清空

问题六:你了解Node中的事件循环吗和浏览器有什么不同?

Node中的宏任务执行有6种阶段:

  • timers定时器阶段,setTimeout,setInterval
  • pengding callback,延迟到下一个循环的IO回调处理
  • idle,prepare,系统内部使用
  • poll,检索新的IO事件和回调
  • check,setImmidiate回调
  • close callback,处理关闭事件的回调,类似socket.on('close',() => {})

Node中的微任务多了一种process.nextTick

而对于宏任务和微任务的处理机制 Node v10版本之前和浏览器不同,它遵循以下顺序:

  • 执行一个宏任务阶段里面的所有任务(嵌套生成同样阶段的宏任务本轮不执行)
  • 然后检查process.nextTick队列并清空
  • 然后检查微任务队列并清空

在V10之后版本Node也和浏览器保持了一直,nextTick算做了一种普通的微任务

问题问完了,我们来几道实战的题目吧

测试一

async function async1() {
    console.log('async1 start');
    await async2();//易错点
    console.log('async1 end');
}
async function async2(params) {
    console.log('async2');
}

console.log('script start');

setTimeout(() => {
    console.log('setTimout');
}, 0);

async1();

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

console.log('script end');

正确答案大家可以自己跑一下试试,注意易错点await async2(); console.log('async1 end');应该怎么理解和运行,我通常都把await后面的代码理解为Promise里面的代码,await下面的剩余代码理解为Promise.then()里面的代码。

测试二

console.log('start');
setTimeout(() => {
    console.log('children2');
    Promise.resolve().then(() => {
        console.log('children3');
    })
}, 0);

new Promise((resolve,reject) => {
    console.log('children4');
    setTimeout(() => {
        console.log('children5');
        resolve('children6');
    }, 0);
}).then((res) => {//易错点
    console.log('children7');
    setTimeout(() => {
        console.log(res);
    }, 0);
})

易错点提示Promise.then是会产生微任务,但是他是什么时候产生的呢,不是你写了then执行到这里就马上产生了微任务,他是在Promise执行了resolve或者reject后才添加到微任务队列里面的哦。

测试三

const p = function (params) {
    return new Promise((resolve,reject) => {
        const p1 = new Promise((resolve,reject) => {
            setTimeout(() => {
                resolve(1);
            }, 0);
            resolve(2);//易错点
        })

        p1.then((res) => {
            console.log(res);
        })

        console.log(3);

        resolve(4);
    })
}

p().then((res) => {
    console.log(res)
})

console.log('end');

易错点提示,Promise的状态只能改变一次,resolve后其他的resolve或者reject都是无效的。

后记鸣谢

前面对于事件循环在浏览器和Node中的不同其实最好用两张图来表示,等有时间我会找一下相关的图片添加进来方便大家理解。这篇文章特别感谢爪哇教育的路白老师,主要的干货内容我都是整理的路老师的公开课。