面试官:讲讲JS的事件循环机制

54 阅读2分钟

众所周知,JS是一门单线程语言,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?

前言

JS 的执行环境一般是浏览器和 Node.js,两者稍有不同,这里只讨论浏览器环境下的情况。

JS 执行过程中会产生两种任务,分别是:同步任务异步任务

  • 同步任务:比如声明语句、for、赋值等,读取后依据从上到下从左到右,立即执行。
  • 异步任务:比如 ajax 网络请求,setTimeout 定时函数等都属于异步任务。异步任务会通过任务队列(Event Queue)的机制(先进先出的机制)来进行协调。

同步任务和异步任务的执行过程

image.png

① 同步任务由JavaScript主线程次序执行

② 异步任务委托给宿主环境执行

③ 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行

④ JavaScript主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行

JavaScript主线程不断重复上面的第4步

EventLoop的基本概念

JavaScript主线程从任务队列中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种机制又称为 EventLoop(事件循环)。

宏任务和微任务

JavaScript把异步任务存放到任务队列中,任务队列中的任务又分为两种,分别是:

  1. 宏任务(Macrotask)
  • 异步Ajax请求
  • setTimeout、setInterval
  • 文件操作
  • 其他宏任务
  1. 微任务(Microtask)
  • Primise.then、.catch 和 .finally(重点
  • process.nextTick(Node.js)
  • MutaionObserver

宏任务和微任务的执行顺序

image.png

每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。

分析以下代码的输出顺序

setTimeout(() => {
     console.log('1');
});

new Promise(function(resolve){
     console.log('2');
     resolve()
}).then(function() {
     console.log('3');
})

console.log('4');

分析:

① 先执行所有的同步任务

  • 执行第6行、第12行代码

② 再执行微任务

  • 执行第9行代码

③ 再执行下一个宏任务

  • 执行第2行代码

正确输出顺序为:2431

经典面试题

请分析以下代码的输出顺序

console.log('1');

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

new Promise(function(resolve) {
    console.log('5');
    resolve()
}).then(function() {
    console.log('6');
})

setTimeout(() => {
    console.log('7');

    new Promise(function(resolve) {
        console.log('8');
    resolve()
    }).then(function() {
        console.log('9');
    })
});

正确输出顺序为:156234789