概念
(事件循环)Event Loop,浏览器中,js引擎线程会循环从任务队列(task queue)中读取事件并且执行,这种运行机制叫做事件循环(event loop)
为什么要了解Event loop
理解Event loop,对于浏览器处理事件的过程会有更透彻的理解,使用promise,nextTick, setTimeout等会更清晰,这些都是平时很经常使用的。
JavaScript运行时的概念
下面的内容解释了一个理论上的模型。现代 JavaScript 引擎着重实现和优化了描述的几个语义。
js是单线程的,脚本执行的概念(来自MDN)
这里面有三个概念:
- 栈(Stack),一种数据结构,特点:先进后出
- 堆(Heap),对象分配在一个堆中,它只是一个名称,用于表示大部分非结构化的内存区域。
- 队列(Queue),avaScript运行时包含消息队列,它是要处理的消息的列表。一个功能与每个消息相关联。当堆栈具有足够的容量时,将从队列中取出消息并进行处理。处理包括调用相关函数(从而创建初始堆栈帧)。当堆栈再次变空时,消息处理结束。 特点:先进先出
函数调用形成一个栈帧
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7));
具体过程描述
- 执行bar(),创建第一个frame,包含bar的参数和局部变量,压入栈。
- 当bar调用foo时,创建第二个frame,包含 foo的参数和局部变量,压入栈。
- 当foo执行完,顶部frame被弹出堆栈
- bar执行完,堆栈变空
Event loop具体过程
先来看一张经典的图
过程描述:
- 先执行同步的代码,然后js跑去消息队列中执行异步的代码,异步完成后,再轮到回调函数,然后去下个事件循环中执行setTimeout
- 它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈(stack)。直到调用栈清空(只剩全局),然后执行所有的micro-task(类似:promise,process.nextTick等)。
当所有可执行的micro-task执行完毕之后。循环再次从macro-task(类似setTimeout)开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
task 里面有microtask和macrotask
涉及的名词:
- task queue.任务队列;
- microtask(微任务),在task queue末尾执行,栈空了也执行; include: setTimeout, setInterval, setImmediate, I/O, UI rendering
- macrotask (宏任务),在第一次task执行完了(即microtask执行完),浏览器进行渲染,然后才执行macrotask; include: process.nextTick, Promises, Object.observe, MutationObserver
- stack执行栈
例子分析
setTimeout(function(){
console.log(4)
}
,0)
new Promise(function(resolve){
console.log(1)
for(var i=0;i<10000;i++){
i===9999 && resolve()
}
console.log(2)
}).then(function(){
console.log(5)
})
console.log(3)
过程描述:
1. js执行setTimeout脚本,压入task queue
2. 遇到promise, 执行promise里的同步部分,输出1,2;promise属于microtask,then()后面的跟随在task末尾
3. 执行console.log(3),输出3
4. 一个task执行完后,执行microtask,输出5
5. 进行浏览器渲染,执行setTimeout(属于macrotask)(macrotask,属于新的task,Event Loop就是指从这里开始一个新的task),输出4
最后个人理解:
浏览器执行js脚本->sync->microtask->macrotask(一个新的task->sync task->microtask不断循环)
参考链接