事件循环Event Loop
我们都知道,JavaScript 是一种单线程的编程语言,简单的说就是:js只有一条通道,那么在任务多的情况下,就会出现拥挤的情况,这种情况下就产生了 ‘多线程’ ,但是这种“多线程”是通过单线程模仿的,也就是假的。
这意味着它一次只能执行一个任务。但是,通过事件循环(Event Loop)机制,可以处理异步操作并确保程序的高效运行。理解事件循环对于我们前端开发者来说至关重要!
事件循环的基本概念
事件循环的核心在于任务队列(Task Queue),它负责管理和调度各种任务的执行。任务分为两类:宏任务、微任务。
事件循环的执行步骤
事件循环的基本执行步骤如下:
- 执行一个宏任务(例如,从头到尾运行一个 script)。
- 执行所有的微任务(一直执行,直到微任务队列为空)。
- 更新渲染(如果需要)。
- 继续执行下一个宏任务。
看不懂没关系,结合下面的例子就能明白了!
宏任务和微任务详解
基于上面的基础知识,我们详细介绍下宏任务和微任务
为什么要区分宏任务和微任务?
(1)js是单线程的,但是分同步异步
(2)微任务和宏任务皆为异步任务,它们都属于一个队列
(3)宏任务一般是:script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 环境)
(4)微任务:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 环境)
(5)先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务
宏任务、微任务有哪些?
- 宏任务一般是:script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 环境)
- 微任务:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 环境)
宏任务、微任务是怎么执行的?
简单来说,宏任务、微任务的执行顺序是:先执行同步代码,异步任务放入任务队列中,当所有同步代码执行完毕后,先执行微任务在执行宏任务。
接下来,我们将通过很多例子,帮助大家深入了解
基本事件循环
setTimeout(function(){console.log(1);
});
new Promise(function(resolve){ console.log(2);resolve();
}).then(function(){ console.log(3);
}).then(function(){console.log(4)
});
console.log(5);
// 2 5 3 4 1
分析
- setTimout是异步宏任务,放入宏任务队列中
- new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2
- Promise.then是异步微任务,将其放入微任务队列中
- 遇到同步任务console.log(5);输出5;主线程中同步任务执行完
- 从微任务队列中取出任务到主线程中,输出3、 4,微任务队列为空
- 从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空
console.log(1)
setTimeout(function() {console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {resolve(4)
})
p.then(data => {console.log(data)
})
console.log(3)
//1,3,4,2
- 遇到同步任务console.log(1);输出1;
- 遇到setTimeout 异步宏任务,放入宏任务队列中;
- 遇到 Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,但由于new Promise没有输出事件,所以接着执行遇到.then;
- 执行.then,异步微任务,被分发到微任务Event Queue中;
- 遇到同步任务console.log(3);输出3;
- 主线程中同步任务执行完,从微任务队列中取出任务到主线程中,p.then 输出4,微任务执行完毕,任务队列为空;
- 开始执行宏任务setTimeout 输出2,宏任务队列为空;
嵌套的宏任务与微任务
console.log('脚本开始')
setTimeout(() => {console.log('这是setTimeout1-------------');Promise.resolve().then(() => {console.log('这是promise1');});
}, 0);setTimeout(() => {console.log('这是setTimeout2');
}, 0);Promise.resolve().then(() => {console.log('这是promise2');
});console.log('脚本结束');
执行顺序:
console.log('脚本开始')
—— 同步代码,立即执行。- 第一个
setTimeout
回调注册到宏任务队列。 - 第二个
setTimeout
回调注册到宏任务队列。 Promise
回调注册到微任务队列。console.log('脚本结束')
—— 同步代码,立即执行。- 当前宏任务执行完毕,执行微任务队列:
console.log('这是promise2')
- 微任务执行完毕,执行第一个宏任务队列:
console.log('这是setTimeout1-------------')
- 注册新的微任务
console.log('这是promise1')
到微任务队列。
- 执行微任务队列:
console.log('这是promise1')
- 执行第二个宏任务队列:
console.log('这是setTimeout2')
输出结果:
脚本开始
脚本结束
这是promise2
这是setTimeout1-------------
这是promise1
这是setTimeout2
多个微任务
console.log('脚本开始');setTimeout(() => {console.log('这是setTimeout');
}, 0);Promise.resolve().then(() => {console.log('这是promise1');
}).then(() => {console.log('这是promise2');
}).then(() => {console.log('这是promise3');
});console.log('脚本结束');
执行顺序:
console.log('脚本开始')
—— 同步代码,立即执行。setTimeout
回调注册到宏任务队列。Promise
回调注册到微任务队列。console.log('脚本结束')
—— 同步代码,立即执行。- 当前宏任务执行完毕,执行微任务队列:
console.log('这是promise1')
- 注册新的微任务
console.log('这是promise2')
到微任务队列。 - 执行微任务队列:
console.log('这是promise2')
- 注册新的微任务
console.log('这是promise3')
到微任务队列。 - 执行微任务队列:
console.log('这是promise3')
- 微任务执行完毕,执行宏任务队列:
console.log('这是setTimeout')
输出结果:
脚本开始
脚本结束
这是promise1
这是promise2
这是promise3
这是setTimeout
总结
通过这些详细的例子,相信大家对宏任务与微任务一定有了更深入的了解。希望本文对大家有帮助~感谢支持