javascript 执行机制
JavaScript 是单线程的,即只有一个调用栈,调用栈按照先入后出的规则执行,一次调用一个,而且可以嵌套,在执行调用栈的时候回先执行同步的任务,同步任务马上就能执行,调用栈在发现异步任务的时候回把异步任务放入队列里面,异步任务队列分为宏任务队列
和微任务队列
,队列都按照先入先出的规则
同步任务和异步任务
flowchart TB A(任务进入执行栈)--> B{同步任务还是异步任务} B --> |同步| C["主线程"] C --> D["任务全部执行完毕"] D --> E["读取任务队列中的结果,进入主线程执行"] B --> |异步| F[Event Table] F --> |注册回调函数| G[Event Queue] G <--> E classDef cdfece fill:#cdfece,stroke:#333,stroke-width:2px; classDef d1e1fe fill:#d1e1fe,stroke:#333,stroke-width:2px; classDef e5cdfe fill:#e5cdfe,stroke:#333,stroke-width:2px; class A,E cdfece; class C,D d1e1fe; class F,G e5cdfe; style B fill:#fd999a,stroke:#333,stroke-width:2px;
- 同步和异步任务分别进入不同的执行场所,同步的进入主线程,异步的进入 Event Table 并注册函数。
- 当指定的事情完成时,Event Table 会将这个函数移入 Event Queue。
- 主线程内的任务执行完毕为空,会去 Event Queue 读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的 Event Loop(事件循环)。
宏任务和微任务
- 宏任务队列
- 新程序或子程序被直接执行
最常见的就是<script>
元素里面的代码就是程序被执行,控制台写代码也算是程序执行 - 事件的回调函数
如:鼠标点击事件触发后,里面的回调函数就会被添加到宏任务队列 - setTimeout()和 setInterval()
- requestAnimationFrame
- I/O 操作
- setImmediate
- UI rendering
- …
- 微任务队列
- Promise.then().catch().finally()
- MutationObserver
- Object.observe
- Node.js 中的 process.nextTick()
事件循环
要知道如何运行任务,需要靠 Event Loop 事件循环,事件循环是一个不断进行循环的机制,事件循环会不断去寻找可以执行的任务来执行,在执行完同步任务以后,也就是清空了调用栈以后,首先会执行微任务队列的任务,把微任务队列的任务清空以后才会去执行宏任务。在进行下一步宏任务之前,浏览器可能会发生渲染的情况,在渲染以后再执行宏任务。
注意: 并不是每个浏览器都把这些函数归为同一类宏任务或微任务,有些浏览器会把 Promise.then()归为宏任务
浏览器运行任务的顺序
- 宏任务 –> 微任务 –> 渲染 –> 宏任务
- 事件循环
- 宏任务 –> 微任务 –> 渲染 –> 宏任务
- 事件循环
- …
首先执行宏任务,因为所有 js 代码都是在 script 中的,在调用栈为空的时候,事件循环优先执行微任务,并且清空微任务队列才会看浏览器是否需要渲染,渲染过后事件循环再执行下一轮宏任务,如果此时宏任务的其中一轮结束后,也就是调用栈清空的时候,事件循环有发现微任务,还会执行微任务,并且清空微任务队列才会看一下是否需要渲染,渲染过后事件循环再执行下一轮宏任务,在没有其余微任务的时候,宏任务就可以一直清空宏任务队列了
宏任务的执行顺序
- timers 定时器:执行已经安排的 setTimeout 和 setInterval 的回调函数
- pending callback 待定回调:执行延迟到下一个循环迭代的 I/O 回调
- idle,prepare:仅系统内部使用
- poll:检索新的 I/O 事件,执行与 I/O 相关的回调
- check:执行 setImmediate() 回调函数
- close callbacks:
socket.on("close", () => {})
微任务和宏任务在 node 的执行顺序
node v10 及以前
- 执行完一个阶段中的所有任务
- 执行 nextTick 队列里的内容
- 执行完微任务队列的内容
node v10 以后
和浏览器的行为统一
实例
1 | async function async1() { |
1 | console.log("start"); |