js是单线程的 为什么要有事件循环机制哩?假设js只是单线程的 碰到同步任务还好,按照顺序依次执行,如果异步任务那种耗时很长的任务,比如发请求,那么这个js任务就卡住了 只能等到请求返回成功才能执行,这样效率就太低了。事件循环机制是为了解决这样的问题,让同步任务能跳过异步任务 并发的执行,等到异步任务完成后回调放入任务队列等待主线程调用执行。
同步任务:在执行栈中按照先进后出的顺序执行,一个执行完了 再轮到下一个执行
异步任务:不阻碍主线程的任务执行
栈 先进后出,可以想像成一头被堵住的管道,只能从一头放入拿出,那先放入的压在下面,只能等上面的拿出后才能拿出。 函数调用时形成调用栈 当调用一个函数时 被压入栈中,函数执行完从栈中移除,执行权交给下一个函数。
堆 对象被分配在堆中,堆是一个大块内存存储区域
队列 先进先出,可以想象成两头通畅的水管,一头流入那一头流出。 js 运行时包含一个待处理消息的消息队列,每个消息都关联一个回调
Js 任务类型:
js代码在执行时 都有一个执行栈,按照先进后出的顺序执行,同步任务执行完从执行栈中弹出,异步任务会把回调放在任务队列中,在异步完成后,判断执行栈是否为空,如果为空会把任务队列中的任务放入执行栈,开启一次事件循环。任务队列中的任务分为两种,微任务和宏任务,先宏后微,第一次整体js代码作为一个宏任务,遇到宏任务就放任务队列,遇到微任务放任务队列,当执行栈空了就从任务队列拿微任务进行处理,执行完所有的微任务。当所有微任务完成,就开启下一轮事件循环,执行宏任务,宏任务执行完执行微任务。
下面例子,执行过程:
console.log('console start');
setTimeout(function() {
console.log('eventLoop2-宏任务1:setTimeout1');
new Promise(function(resolve) {
console.log('promise2');
resolve()
}).then(function() {
console.log('eventLoop2-微任务1:then2');
}).then(function() {
console.log('eventLoop2-微任务2:then22');
})
})
setTimeout(function() {
console.log('eventLoop3-宏任务1:setTimeout2');
})
new Promise(function(resolve) {
console.log('promise');
resolve()
}).then(function() {
console.log('eventLoop1-微任务1:then');
}).then(function() {
console.log('eventLoop1-微任务2:then1');
})
console.log('console end');
// console start
// promise
// console end
// eventLoop1-微任务1:then
// eventLoop1-微任务2:then1
// eventLoop2-宏任务1:setTimeout1
// promise2
// eventLoop2-微任务1:then2
// eventLoop2-微任务2:then22
// eventLoop3-宏任务1:setTimeout2
同步代码放入执行栈 执行后弹出 输出 console start。整体代码作为宏任务放入宏任务队列
接着setTimeout放入执行栈 执行后弹出,setTime callback (eventLoop2-宏任务1) 放入宏任务任务队列
接着setTimeout放入执行栈 执行后弹出,setTime callback(eventLoop3-宏任务1)放入宏任务任务队列
new Promise 放入执行栈执行后弹出,输出 promise,promise.then callback(eventLoop1-微任务1)放入微任务队列 promise.then.then callback(eventLoop1-微任务2)放入微任务队列
执行同步代码 输出console end 此时执行栈为空
开启第一次事件循环,从宏任务队列对头那任务,执行整体宏任务,询问微任务是否为空
微任务队列不为空执行微任务队列,promise.then callback(eventLoop1-微任务1)放入执行栈执行完退出,输出 eventLoop1-微任务1:then
继续询问微任务队列是否为空 不为空 promise.then.then callback(eventLoop1-微任务2)放入执行栈 执行完退出 输出 eventLoop1-微任务2:then
继续询问微任务队列是否为空 为空。开启下一轮事件循环
拿宏任务执行,把setTime callback (eventLoop2-宏任务1)放入执行栈 执行完退出,打印eventLoop2-宏任务1:setTimeout1,继续执行new Promise 输出primise2,把 promise2的回调eventLoop2-微任务1:then2 和eventLoop2-微任务2:then22放入微任务队列
宏任务执行完 继续询问微任务 ,执行微任务 打印 eventLoop2-微任务1:then2 和 eventLoop2-微任务2:then22
微任务队列为空 开启第三轮事件循环 执行setTime callback(eventLoop3-宏任务1) 打印eventLoop3-宏任务1:setTimeout2
setTimeout(()=> {},0) 不是立即执行 而是等到执行栈为空再执行
setTimeout(()=> {},1000) 不是1秒后执行 1秒只是最快执行时间
事件循环和浏览器渲染
并不是每一次事件循环都伴随着一次浏览器渲染,由屏幕刷新频率 页面性能 等共同决定。浏览器会尽可能保证帧率的稳定,如果无法维持60fps会降低到30fps 。
requestAnimationFrame 在重新渲染前调用
setTimeout 浏览器会把几次任务合并
setTimeout(() => {
console.log("sto")
requestAnimationFrame(() => console.log("rAF"))
})
setTimeout(() => {
console.log("sto")
requestAnimationFrame(() => console.log("rAF"))
})
queueMicrotask(() => console.log("mic"))
queueMicrotask(() => console.log("mic"))
requestIdleCallback 空闲调度算法 在屏幕渲染后执行,把一些计算量较大但没那么紧急的任务放到空闲时间去执行 不要影响浏览器中优先级高的任务。