事件循环是js这门语言的一大特点。
了解事件循环机制,有助于日常开发中遇到的一些异步问题。
而且还是前端面试一经常考点。
故本人结合一些文章和个人的一些开发经验,浅淡一下
一,js是一门单线程语言
js的单线程
a. js是一门单线程的语言。这意味着它在同一时间,只能做同一件事。
b. 但为了协调事件,用户交互,UI渲染和网络行为交互等。
c. 防止主线程被阻塞,Event Loop便应运而生。
如: 发送一个网络请求,需要等待一定时间,这个时间内主线程空闲出来做些其他事;
为什么js是单线程?
a. js主要是运行在浏览器的脚步语言,主要是操作dom;
b. 举个例子,如果js同时有多个线程。多个线程同时操作同一个dom,
这时浏览器该依据那个线程,如何判断优先级
c. 为了避免上述问题,并降低复杂度,故js被设计成单线程语言。
二,概念的理解
同步任务
同步任务指的是,在主线程上排队执行的任务,
只有前一个任务执行完毕,才能执行后一个任务;
异步任务
异步任务指的是,不进入主线程、而进入"任务队列" (task queue)的任务,
只有"任务队列" 通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步执行机制
a. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack);
b. 主线程之外,还存在一个"任务队列" (task queue)。
只要异步任务有了运行结果,就在"任务队列" 之中放置一个事件。
c. 一旦"执行栈" 中的所有同步任务执行完毕,系统就会读取"任务队列" ,
看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,
进入执行栈,开始执行。
d. 主线程不断重复上面的第三步。
任务队列
"任务队列" 是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。
事件循环
主线程从"任务队列" 中读取事件,这个过程是循环不断的,
所以整个的这种运行机制又称为Event Loop(事件循环)。
宏任务与微任务
异步任务分为 宏任务(macrotask) 与 微任务 (microtask),
不同的API注册的任务会依次进入自身对应的队列中,
然后等待 Event Loop 将它们依次压入执行栈中执行。
宏任务:script(整体代码)、setTimeout、setInterval、UI 渲染、
I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
微任务:Promise 、 MutaionObserver、process.nextTick(Node.js环境)
Event Loop(事件循环)
(1 )执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行;
(2 )检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列;
(3 )更新render(每一次事件循环,浏览器都可能会去更新渲染);
(4 )重复以上步骤;
宏任务 > 所有微任务(核心),上代码
<script>// 宏任务1
console.log('宏任务1'); // 宏任务1中的同步任务
setTimeout(() => {// 宏任务1中的另一个宏任务3
console.log('宏任务1中的另一个宏任务3');
new Promise((resolve, reject) => {
resolve('宏任务3中的微任务2');
}).then(data => {// 宏任务3中的微任务2
console.log(data)
})
}, 300);
new Promise((resolve, reject) => {
resolve('宏任务1中的微任务1');
}).then(data => {// 宏任务1中的微任务1
console.log(data);
setTimeout(() => {// 微任务1中的另一个宏任务4
console.log('微任务1中的另一个宏任务4');
}, 300);
});
</script>// 宏任务1
<script>// 宏任务2
console.log('宏任务2')
</script>// 宏任务2
1. 宏任务1 =>宏任务1 中的微任务1
表明执行完宏任务就执行微任务(忽略宏任务2 ,便于理解)
2. 然后到 宏任务1 中宏任务3 =>宏任务3 中的微任务2
再次表明执行完本宏任务后就执行本宏任务下的微任务
3. 最后到微任务1 中的宏任务4