一、event loop(事件循环/事件轮询)机制
-
js是单线程运行的
-
异步要基于回调来实现
-
event loop 就是异步回调的实现原理
1.1、 js如何执行
- 从前往后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
console.log('start')
setTimeout(function cb1(){
console.log('cb') // cb 即 callback
}, 500)
console.log('end')
执行结果
1.2 event loop 的执行过程
1、同步代码,一行一行放在CallStack执行
2、遇到异步,会先“记录”下,等待时机(定时、网络请求等)
3、时机到了,就移动到Callback Queue
4、如Call Stack为空(即同步代码执行完)Event Loop开始工作
5、轮询查找Callback Queue,如有则移动到Call Stack执行
6、然后继续轮询查找(永动机一样)
1.3 代码执行过程:
1. 将 console.log("Hi") 推入调用栈,调用栈会执行代码
2. 执行代码,控制台打印“Hi”,调用栈清空
3. 执行 setTimeout,setTimeout由浏览器定义,不是ES6的内容;
将定时器放到Web APIs中,到时间后将回调函数放到回调函数队列中
4. 执行完了setTimeout, 清空调用栈
5. console.log("Bye")进入调用栈,执行,调用栈清空
6. 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制
7. 五秒之后,定时器将cb1推到回调函数队列中
8. 事件循环将cb1放入调用栈
二、DOM事件和event loop的关系
dom事件也使用回调,基于 event loop 但是 dom事件不是异步的,但是它们都是基于异步的
- js 是单线程的
- 异步(setTimeout, ajax等)使用回调,基于 event loop
- dom事件也使用回调,基于 event loop 但是 dom事件不是异步的,但是它们都是基于异步的
三、 什么是宏任务macroTask和微任务microTask
3.1 代码示例
console.log(100)
setTimeout( () => {
console.log(200)
})
Promise.resolve().then(()=>{
console.log(300)
})
console.log(400)
代码执行 顺序打印 100 400 300 200
js中执行顺序:
宏任务——>微任务——>DOM页面重渲染——>宏任务(循环)
宏任务主要有:
setTimeout、setInterval、Ajax、DOM事件
微任务:
Promise、async/await
微任务执行比宏任务要早
微任务执行比宏任务要早(其实不太对,因为script标签执行解析也属于宏任务)
四、event-loop和DOM渲染的关系
js是单线程,且和DOM渲染共用同一个线程
因此js执行时,需要留一些时机给DOM渲染。
如下面代码所示,分析下DOM渲染的时机
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
console.log('length', $('#container').children().length )
如下图所示, 其实有DOM 渲染的流程
- 每次Call Stack清空(即每次轮询结束),即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再去触发下一次 Event Loop
五、 为什么微任务比宏任务执行更早?
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
console.log('length', $('#container').children().length )
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预
代码执行效果
上图可以看到,alert() 执行,但是DOM 还没有渲染
我们点击alert 弹窗的确认,DOM渲染 如下图所示
微任务和宏任务的区别:
- 宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
所以微任务执行时机比宏任务要早
// 修改 DOM
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
// Promise.resolve().then(() => {
// const length = $('#container').children().length
// alert(`micro task ${length}`)
// })
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
const length = $('#container').children().length
alert(`macro task ${length}`)
})
六、微任务和宏任务的根本区别
再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后?
微任务和宏任务在event loop会放在不同的地方等待
-
微任务是ES6语法规定的(promise、async/await)
-
宏任务是由浏览器规定的(setTimeout/setInterval、Ajax、DOM)
- 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
- 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。