浅谈浏览器运行环境下的event loop机制

165 阅读2分钟

先来看一张图片

主线程调用栈

所谓的JS单线程是指一个浏览器的主进程中只有一个JS执行线程。JS的执行线程的调用栈(stack)都是同步的(因为是单线程,所以一次只能做一件事),JS执行线程的主线程上的所有同步的任务都会放在这个执行栈中。

console.log('event loop1');
console.log('event loop2');

简单如以上代码,就被放在Call Stack执行栈中顺序执行,然后遵循先进后出,后进先出的原则销毁。输出的顺序是:先是event loop1后是 event loop2。

事件循环机制

如果执行过程中遇到DOM操作、ajax或setTimeout等异步操作的时候,会提交给相应的异步进程处理。

如:DOM操作是浏览器内核的DOM binding模块处理;Ajax是浏览器内核的Network模块处理;setTimeout是由浏览器内核的Timer模块处理。

处理完的回调就被推入到任务队列callback queue(应该叫event queue可能更准确些)。等待主线程的调用栈空闲。

一旦主线程的task执行完毕后,浏览器执行环境的Event Loop机制就查询任务队列,把队列里排在前面的回调压入主调用栈执行,如此重复。这个过程就称为事件循环

所以我们就可以很容易理解以下代码:

console.log('hello');
setTimeout(function(){
    console.log('setTimeout1');
})
setTimeout(function(){
    console.log('setTimeout2');
})
console.log('world');

结果依次是:hello world setTimeout1 setTimeout2


任务队列

任务队列存在两种类型,一种是microtask queue,还有一种是macrotask queue。

macro-task(宏任务)包括:script(整体代码),setTimeout,setInterval, setImmediate(node.js),I/O,UI rendering。

micro-task(微任务)包括:process.nextTick(node.js),Promises,Object.observe, MutationObserver。

事件循环主线程进入调用栈开始,看到setTimeout处理完就会添加macrotask 队列中。看到Promises处理完就会添加到microtask队列中。等到主线程调用栈都执行完任务后,会优先按执行microtask queue,全部执行完。然后才会取macrotask queue排在队首的执行。

看下面这段代码:

console.log(1);
setTimeout(() => {
    console.log(2);
}, 0);
new Promise((resolve, reject) => {
    console.log(3);
    Promise.resolve().then(() => {
        console.log(4);
    })
    resolve();
}).then(() => {
    console.log(6);
})
console.log(7);

执行顺序依次是:1 3 7 4 6 2 。这说明mic-rotask中的回调将优先执行。

ps:这个网站可以实现代码的可视化 latentflip.com/loupe/,有兴趣的可以看看。

参考文章:

zhuanlan.zhihu.com/p/26238030 blog.csdn.net/lin_credibl…