事件循环 js是单线程的 为什么要有事件循环机制哩?假设js只是单线程的 碰到同步任务还好,按照顺序依次执行,如果异步任务那种耗时很长的任务,比如发请求,那么这个js任务就卡住了 只能等到请求返回成功才能执行,这样效率就太低了。事件循环机制是为了解决这样的问题,让同步任务能跳过异步任务 并发的执行,等到异步任务完成后回调放入任务队列等待主线程调用执行。
- 同步任务:在执行栈中按照先进后出的顺序执行,一个执行完了 再轮到下一个执行
- 异步任务:不阻碍主线程的任务执行
- 栈 先进后出,可以想像成一头被堵住的管道,只能从一头放入拿出,那先放入的压在下面,只能等上面的拿出后才能拿出。 函数调用时形成调用栈 当调用一个函数时 被压入栈中,函数执行完从栈中移除,执行权交给下一个函数。
- 堆 对象被分配在堆中,堆是一个大块内存存储区域
- 【重学前端|重学前端——事件循环】队列 先进先出,可以想象成两头通畅的水管,一头流入那一头流出。 js 运行时包含一个待处理消息的消息队列,每个消息都关联一个回调
Js 任务类型:
- 宏任务:script代码 setTimeout setInterval setImediate I/O UIRendering
- 微任务: Process.nextTick Promise Mutation.Observer
下面例子,执行过程:
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 空闲调度算法 在屏幕渲染后执行,把一些计算量较大但没那么紧急的任务放到空闲时间去执行 不要影响浏览器中优先级高的任务。
推荐阅读
- 重学前端|重学前端-面向对象
- ts|typescript ts 基础知识之用webpack打包ts代码
- js|小程序和h5通信
- 前端基础|众多mock工具,这一次我选对了
- egg|uniapp+后端egg.js 开发微信小程序获取用户手机号
- Vue2专栏|vue组件通信案例练习(包含(父子组件通信及平行组件通信))
- vue.js|vue组件间通信方式(父子通信、兄弟通信、跨级通信)
- vue基础|vue的第一个程序(包括简介)
- 前端|《前端》vue的自我修养(webpack版)--export ,export default 和 import 区别以及用法