我所理解的Event-loop
javaScript的执行机制(Event-Loop)
对于一个前端工程师来说,理解js的执行机制是一件至关重要的事情
1 | var a = 10; |
当我们接触到这样的代码,我们心情舒畅,因为一眼就可以看出我们应该先执行那个步骤,再执行哪个步骤,随后我们根据需求添加了定时器
1 | var a = 10; |
我们遇到了小小的麻烦,执行过程需要考虑定时器的使用,这还算可以接受,但是随着需求增加我们使用定时器
,async
,promise
等等异步操作的次数也会越来越多,我们会觉得执行更加复杂,甚至心态爆炸,因此我们必须理解js的执行机制,才能有效解决这个难以处理的问题
javaScript的事件循环
我们总会听到js是一门单线程语言,虽然随着语言的发展,技术大牛们也在探索多线程的发展,但是至今为止所有的类似多线程都是用单线程模拟出来的
由于Js是单线程,所以正常情况下代码是顺序执行的,但是很多时候有些代码不需要直接执行,因此我们把javascript中单线程任务分为以下两类:
- 同步任务
- 异步任务
任务队列
任务队列task Queue,即队列,是先进先出的数据结构
MacroTask(宏任务)
- js的全部代码,
setimeout
、setInterval
、setImmediate
、I/O
、UI Rendering
MicroTask(微任务)
Process.nextTick()
、Promise
、Object.observe(废弃)
、MutationObserver
从以上导图可以看出:
- 同步与异步任务分别在不同场所执行,同步任务进去主线程,异步任务进入
Event Table
并为异步任务注册回调函数 - 任务完成后
EventTable
会将任务放入EventQueue
- 主线程内的任务执行完毕后任务队列此时为空,任务队列会读取
Event Queue
中的任务加入队列,进入主线程执行 - 以上过程不断重复,即为事件循环(Event Loop)
js引擎中存在monitoring process
进程,它会不断地对主线程任务栈进行检查,一旦任务栈为空,则会去Event Queue
检查是否有等待调用的函数
Event Table中的执行过程
执行栈在执行玩同步任务后,查看执行栈是否为空,如果执行栈为空,则查微任务(microTask
)栈是否为空,如果不为空,则执行完成所有的微任务,如果为空则执行宏任务(Tasks
)
每一次宏任务执行完毕,都检查微任务队列是否为空,不为空则按照出对原则(先入先出)执行所有微任务,然后设置微任务队列为null
,然后执行宏任务,如此循环
简单示例
Ajax
ajax技术是最常用到的数据异步数据请求技术,作为异步请求,他的执行顺序完全符合上面的流程
1 | let data = []; |
上面的代码执行顺序就是:
- ajax为异步任务进入Event Table,注册回调函数success
- 顺序执行主线程任务打印
代码执行结束
Event Table
中ajax执行结束,success
被放入Event Queue
- 主线程执行完毕,任务栈为空,主线程从
Event Queue
读取success
函数放入主线程执行 - 此时打印
数据传输成功
setTimeout
setTimeout
是延时执行函数,同样属于异步操作,总见到有人将setTimeout
的延时设置为0,其实根据HTML标准,无论怎么设置,最低标准等待时间都是4ms
1 | console.log("start") |
上面代码的执行过程是这样的:
- 打印
start
- 定时器为异步操作,放入Event Table, 注册回调函数
- 主任务栈中执行打印
end
- Event Table中定时器执行完毕,打印操作放入Event Queue
- 主任务栈为空,Event Table中的任务放入主线程执行,打印
延时操作
复杂示例(面试题)
1 | console.log('script start'); |
执行上述代码时,我们先将代码进行分类:
1 | Tasks: run script、setTimeout callback //宏任务 |
执行同步代码: 打印script start
,script end
划分宏任务与微任务进行详细划分
1 | Tasks: run script、setTimeout callback //宏任务 |
宏任务执行完毕后,任务栈为空,查询微任务,发现Promise,执行Promise,打印promise1
,将.then()回调函数放入微任务
1 | Tasks: run script、setTimeout callback //宏任务 |
继续执行微任务,打印Promise2
,微任务栈随即清空
1 | Tasks: setTimeout callback //宏任务 |
继续执行宏任务,执行setTimeout callback打印setTimeout,随即宏任务与微任务全部清空
async/await
async/await
是在ES6中加入到javascript的标准异步处理方式
javascript在底层已经将async/await转换为promise和then回调函数
终极示例
这是一个摘自掘金作者作者:ssssyoki 文章中的例子,我研究过后才真正理解了Event Loop的机制。
1 | console.log('1'); |
上述代码的执行顺序如下:
首先执行同步任务 :
1
2
3
4
5
6Tasks: script, setTimeout1 callback, setTimeout2 callback //宏任务
MicroTasks: process.nextTick1, Promise.then()//微任务
JsTask: script
log: 1,7同步任务执行结束查询微任务栈,发现微任务,执行微任务:
1
2
3
4
5
6Tasks: script, setTimeout1 callback, setTimeout2 callback //宏任务
MicroTasks: //微任务
JsTask: Promise.then()
log: 1,7,6,8微任务执行结束,执行宏任务:
1
2
3
4
5
6Tasks: setTimeout1 callback, setTimeout2 callback //宏任务
MicroTasks: process.nextTick, Promise.then()//微任务
JsTask: setTimeout1 callback
log: 1,7,6,8,2,4宏任务执行结束,查询并执行微任务:
1
2
3
4
5
6Tasks: setTimeout1 callback, setTimeout2 callback //宏任务
MicroTasks: //微任务
JsTask: Promise.then()
log: 1,7,6,8,2,4,3,5微任务执行结束,继续执行宏任务,发现setTimeout2:
1
2
3
4
5
6Tasks: setTimeout2 callback //宏任务
MicroTasks: process.nextTick, Promise.then()//微任务
JsTask: setTimeout2 callback
log: 1,7,6,8,2,4,3,5,9,11宏任务执行结束,查询微任务,执行并清空微任务:
1
2
3
4
5
6Tasks: setTimeout2 callback //宏任务
MicroTasks: //微任务
JsTask:
log: 1,7,6,8,2,4,3,5,9,11,10,12process.nextTick属于微任务又优先于所有微任务,有process.nextTick存在时应优先执行process.nextTick。
总结
javaScript时一门单线程的语言,js的执行机制为事件循环(Event Loop),理解了这一概念将会更有利于之后与js相关知识的学习。
⚠️ javaScript在浏览器和node中的运行机制并不一致,以上的机制只适用于浏览器环境,并不适用于node环境^-^