浏览器的事件循环机制

Javascript是一个单线程、非阻塞、异步、解释性脚本语言。js的并发模型基于事件循环,Event Loop是由js宿主环境,如浏览器实现的。v8是Chrome里的javascript运行环境,在V8的源码中并不存在setTimeout/DOM/HTTP请求等 ,这些异步请求在浏览器中由webAPI处理,它是由C++实现的浏览器创建的线程。

以下是浏览器中事件循环机制的流程图,只要执行栈中没有代码在执行,微任务会在回调后立即执行。


浏览器事件循环机制流程图

关于宏任务、微任务的说法有争议,ecma-262 中称之为JobsJob Queues,这里对宏任务和微任务的理解是参考 Tasks, microtasks, queues and schedules 和 事件循环处理模型,并且不同浏览器对一些异步事件的执行机制有所不同,这里只考虑Chrome情况下的执行顺序。此外这个视频 到底什么是Event Loop 比较基础地介绍了事件循环机制。

以下各个例子有助于加深理解事件循环机制,可自己思考得出结果后再对比运行结果 。

e.g.1

Promise.resolve().then(function promise1 () {
       console.log('promise1');
    })
setTimeout(function setTimeout1 (){
    console.log('setTimeout1')
    Promise.resolve().then(function  promise2 () {
       console.log('promise2');
    })
}, 0)

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

e.g.2

new Promise(resolve => {
    resolve(1)
    Promise.resolve().then(() => console.log(42)) 
    console.log(4)
}).then(t => console.log(t)) 
console.log(3)

e.g.3

async函数是promise的语法糖,await中的语句相当于在promise.resolve中,相当于同步任务,会被立即加入执行栈;await后的语句相当于在promise.then中,如果微任务中嵌套有宏任务,会在执行到该微任务后再将宏任务进入宏任务队列,从而进入下一轮事件循环

console.log('script start')
async function async1() {
    await async2();
    console.log('async1 end');
    setTimeout(function() {
        console.log('async1 setTimeout')
    }, 0);
}
async function async2() {
    console.log('async2 end');
    setTimeout(function() {
        console.log('async2 setTimeout')
    }, 0);
}
async1();

setTimeout(function() {
    Promise.resolve().then(function() {
        console.log('setTimeout promise');
    })
    console.log('setTimeout');
}, 0);

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

console.log('script end')

e.g.4

执行下面 两个例子时会发现与预期有出入,是因为:

  • promise.nextTick是放到当前执行栈的尾部,一定比异步的任务队列早,并不是因为优先级高于其他异步任务。
  • 根据 mdn setTimeout()/setInterval() 的每调用一次定时器的最小间隔是4ms,参考 底层源码,第一个例子中,传入1ms和0ms最后执行的都是1ms,第二个例子中的两个setTimeout延时相同,所以被合入了一个宏任务一起执行。
setTimeout(() => {
    console.log(2)
}, 2)

setTimeout(() => {
    console.log(1)
}, 1)

setTimeout(() => {
    console.log(0)
}, 0)
console.log(1)

setTimeout(() => {
   console.log(2)
   new Promise(resolve => {
       console.log(4)
       resolve()
   }).then(() => {
       console.log(5)
   })
   process.nextTick(() => {
       console.log(3)
   })
})

new Promise(resolve => {
   console.log(7)
   resolve()
}).then(() => {
   console.log(8)
})

process.nextTick(() => {
   console.log(6)
})

setTimeout(() => {
   console.log(9)
   process.nextTick(() => {
       console.log(10)
   })
   new Promise(resolve => {
       console.log(11)
       resolve()
   }).then(() => {
       console.log(12)
   })
})

浏览器与node事件循环的根本区别就是:浏览器是执行完一个宏任务后就会清空微任务队列,node是将同源的宏任务执行完毕之后再去清空微任务队列,简单理解就是宏观任务可以进行合并。可以用下面这个例子在浏览器和node上自行运行试验

console.log(1);

setTimeout(() => {
    console.log(2)
    new Promise((resolve) => {
        console.log(6);
        resolve(7);
    }).then((num) => {
        console.log(num);
    })
});

setTimeout(() => {
    console.log(3);
       new Promise((resolve) => {
        console.log(9);
        resolve(10);
    }).then((num) => {
        console.log(num);
    })
    setTimeout(()=>{
        console.log(8);
    })
})

new Promise((resolve) => {
    console.log(4);
    resolve(5)
}).then((num) => {
    console.log(num);
    new Promise((resolve)=>{
        console.log(11);
        resolve(12);
    }).then((num)=>{
        console.log(num);
    })
})

参考
浏览器中的事件循环
Event Loop的规范和实现
浏览器中的事件循环
https://github.com/yangxy6/FE_Learning/issues/2
https://learnku.com/articles/38802
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/26

你可能感兴趣的:(浏览器的事件循环机制)