如vue官网的描述:
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的
Promise.then
、MutationObserver
和setImmediate
,如果执行环境不支持,则会采用setTimeout(fn, 0)
代替。
以上出现了事件循环
的概念,其涉及到JS的运行机制,包括主线程的执行栈
、异步队列
、异步API
、事件循环
的协作,我们接下来先简单了解一下 JS 的运行机制。
二、JS 运行机制
JS 执行是单线程的,它是基于事件循环
的。事件循环大致分为以下几个步骤:
-
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
-
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
-
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
-
主线程不断重复上面的第三步。
主线程的执行过程就是一个 tick,而所有的异步结果都是通过 “任务队列” 来调度。消息队列中存放的是一个个的任务(task)。规范中规定 task 分为两大类,分别是 macro task
和 micro task
,并且每个 macro task
结束后,都要清空所有的 micro task
。执行顺序如下:
for (macroTask of macroTaskQueue) {
// 1. Handle current MACRO-TASK
handleMacroTask();
// 2. Handle all MICRO-TASK
for (microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
接下来,我们来了解一下macro task
和 micro task
的重要概念。
2.1 macro task
宏任务,称为task
-
macro task作用是为了让浏览器能够从内部获取javascript / dom的内容并确保执行栈能够顺序进行。
-
macro task调度是随处可见的,例如解析HTML,获得鼠标点击的事件回调等等。
2.2 micro task
微任务,也称job
-
micro task通常用于在当前正在执行的脚本之后直接发生的