初认识:
javaScript事件循环进制是一个用于处理异步事件的回调函数的机制。是JavaScript实现异步编程的核心。
- 用于管理任务队列和调用栈,以及在适当的时候执行回调函数。
- 可以监听消息队列中的事件,并根据事件处理的优先级顺序来依次执行相应的回调函数。
事件循环机制允许JavaScript在等待某些任务(异步任务:通常需要一定时间才能完成)完成的同时,可以执行其他任务,避免了阻塞,解决了单线程并发性的问题。
JavaScript事件循环是为了解决JavaScript的单线程并发性问题,实现单线程非阻塞。javaScript是一个单线程,同一时间只能执行一件事情。这样单一的特性会导致JavaScript在处理长时间运行的操作的时候出现阻塞,阻塞主要还是异步任务的问题。
基础:
- 同步任务:只有前一个任务执行完成之后,才能执行后一个任务。Promise()的参数,在async函数中的await右边的代码都是作为同步代码执行的。
- 异步任务:在执行这个任务的同时,可以执行别的任务,异步任务都是耗时的。不进入主线程,进入“任务队列"的任务,当主线程的任务运行完了之后,才会从“任务队列”取出,放入主线程执行。
微任务(microtask):
- promise,只有Promise.then()的回调函数才会放入微任务中
- async (async函数中的await右边的代码是同步代码)
- await
- process.nextTick(node)
- mutationObserve
宏任务(macrotask):
- script
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
执行顺序(优先级):同步任务 -> 微任务 -> 宏任务。不断重复这个过程。(JS引擎指向代码是从上往下执行)
宏任务中的微任务执行顺序:宏任务执行完之后,微任务就一个接一个的执行。
在一轮循环中,会先执行一个宏任务,然后把微任务队列中的所有任务全部执行。然后在下一轮循环中,继续这样执行。
事件循环:
参考信息:并发模型与事件循环 - JavaScript | MDN (mozilla.org)
可视化展示:
事件循环组成:
参考资料:事件循环机制-执行栈、调用栈 - 渣渣逆天 - 博客园 (cnblogs.com)
一看就懂的事件循环机制(event loop) - 掘金 (juejin.cn)
调用栈(Stack):
调用栈用于存储在代码执行阶段创建的所有执行上下文。
js有且只有一个调用栈,因为js在某个时刻只能做一件事。
调用栈的目的是为了帮助 JS编译器 用于追踪函数被调用的顺序的一种机制。
演示:
const second = () => {
console.log('Hello there!')
}
const first = () => {
console.log('Hi there!')
second();
console.log('The End')
}
first()
堆(heap):
保存的地址。
对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。
队列(Queue):
保存事件对应的回调函数,在事件执行时,被弹出。
一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。
在 事件循环 期间的某个时刻,会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。
函数的处理会一直进行到栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。
Web API:
浏览器提供了多种异步的Web API,如DOM,times(计时器),AJAX等。
当我们调用一个 Web API 时,如 setTimeout,setTimeout函数会被 push 调用栈顶然后执行,但是 setTimeout 的回调函数不会立即被 push 到调用栈顶,而是起一个计时器任务。当这个计时器结束时,该回调函数会被塞到任务队列(CallBack Queue)中。这个队列中的回调函数的调用就是由事件循环机制来控制的。
理解: 调用任务时,并不会马上进入任务队列,而是先调用web提供的api,等待执行的结果才放进去任务队列
事件循环效果展示视频:一个动画搞清前端js事件循环,面试再也不慌了_哔哩哔哩_bilibili
举栗子🌰:
参考视频:面试官:说一说事件循环?_哔哩哔哩_bilibili
栗子1:
console.log('script start');
async function async1(){// 微任务
await async2();// 同步任务
console.log('async1 end');
}
async function async2(){// 同步任务
console.log('async2 end');
}
async1()
setTimeout(function(){// 宏任务
console.log('setTimeout1');// 同步任务
new Promise(res => res())
.then(()=>{
console.log('promise in timer1');
})
.then(()=>{
console.log('promise in timer2');
})
},0)
setTimeout(function(){// 宏任务
console.log('setTimeout2');
},0)
new Promise(resolve => {// 微任务
console.log('promise1');// 同步任务
resolve()
console.log('promise2');
})
.then(function(){
console.log('promise3');
})
.then(function(){
console.log('promise4');
})
console.log('script end');
// 结果
script start
async2 end
promise1
promise2
script end
async1 end
promise3
promise4
setTimeout1
promise in timer1
promise in timer2
setTimeout2
栗子2:
new Promise(resolve=>{
console.log('promise1');
resolve()
console.log('promise2');
})
.then(function(){
console.log('promise3');
setTimeout(function(){
console.log('setTimeout3');
},0)
})
.then(function(){
console.log('promise4');
setTimeout(function(){
console.log('setTimeout4');
},0)
})
.then(function(){
console.log('promise5');
setTimeout(function(){
console.log('setTimeout5');
},0)
})
// 结果
promise1
promise2
promise3
promise4
promise5
setTimeout3
setTimeout4
setTimeout5