浅析 Vue 2.6 中的 nextTick 要领。
事宜轮回
JS 的 事宜轮回 和 使命行列 实际上是明白 nextTick 观点的症结。
这个网上实在有许多优良的文章做了细致引见,我就简朴过过了。
以下内容适用于浏览器端 JS,NodeJS 的事宜轮回机制并不相同。
范例中划定 task 分为两大类: task(macrotask)
和 microtask
。
一般认为是 task
的使命源:
setTimeout / setInterval
setImmediate
MessageChannel
I/O
UI rendering
一般认为是 microtask
的使命源:
Promise
process.nextTick
MutationObserver
Object.observe(已烧毁)
简朴概略:(这里是官方范例)
- 起首最先实行 script 剧本,直到实行上下文栈为空时,最先清空 microtask 行列里的使命,行列嘛,先入先出,出一个实行一个,清空终了,走事宜轮回。
- 事宜轮回:不断地去取 task 行列的中的一个使命推入栈中实行,并在当次轮回里顺次清空 microtask 行列里的使命,清空以后,能够会触发页面更新衬着(由浏览器决议)。
- 以后反复 事宜轮回 步骤。
nextTick
Vue 中数据的变化到 DOM 的更新衬着是一个异步历程。
此要领便用于在 DOM 更新轮回完毕以后实行耽误回调。
运用要领很简朴:
// 修正数据
vm.msg = 'Hello';
// DOM 还没有更新
Vue.nextTick(function() {
// DOM 更新了
});
// 作为一个 Promise 运用
Vue.nextTick().then(function() {
// DOM 更新了
});
源码 去除解释,实在也只要不到一百来行,团体照样很轻易明白的。
这里划成 3 个部份引见。
模块变量
引见 引入的模块 和 定义的变量。
// noop 空函数,可用作函数占位符
import { noop } from 'shared/util';
// Vue 内部的错误处置惩罚函数
import { handleError } from './error';
// 推断是IE/IOS/内置函数
import { isIE, isIOS, isNative } from './env';
// 运用 MicroTask 的标识符
export let isUsingMicroTask = false;
// 以数组情势存储实行的函数
const callbacks = [];
// nextTick 实行状况
let pending = false;
// 遍历函数数组实行每一项函数
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
}
异步耽误函数
接下来是中心的 异步耽误函数。这里差别的 Vue 版本采纳的战略实在并不相同。
2.6 版本优先运用 microtask 作为异步耽误包装器。
2.5 版本则是 macrotask 连系 microtask。然则,在重绘之前状况转变时会有小题目(如 #6813)。另外,在事宜处置惩罚递次中运用 macrotask 会致使一些没法躲避的新鲜行动(如#7109,#7153,#7546,#7834,#8109)。
所以 2.6 版本如今又改用 microtask 了,为何是又呢。。由于 2.4 版本及之前也是用的 microtask。。。
microtask 在某些情况下也是会有题目的,由于 microtask 优先级比较高,事宜会在递次事宜(如#4521,#6690 有变通要领)之间甚至在一致事宜的冒泡历程当中触发(#6566)。
// 中心的异步耽误函数,用于异步耽误挪用 flushCallbacks 函数
let timerFunc;
// timerFunc 优先运用原生 Promise
// 底本 MutationObserver 支撑更广,但在 iOS >= 9.3.3 的 UIWebView 中,触摸事宜处置惩罚递次中触发会发生严重错误
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
// IOS 的 UIWebView,Promise.then 回调被推入 microtask 行列然则行列能够不会准期实行。
// 因而,增加一个空计时器“强迫”实行 microtask 行列。
if (isIOS) setTimeout(noop);
};
isUsingMicroTask = true;
// 当原生 Promise 不可用时,timerFunc 运用原生 MutationObserver
// 如 PhantomJS,iOS7,Android 4.4
// issue #6466 MutationObserver 在 IE11 并不牢靠,所以这里排除了 IE
} else if (
!isIE &&
typeof MutationObserver !== 'undefined' &&
(isNative(MutationObserver) ||
// PhantomJS 和 iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
// 假如原生 setImmediate 可用,timerFunc 运用原生 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 末了的顽强,timerFunc 运用 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
一句话总结优先级:microtask 优先。
Promise > MutationObserver > setImmediate > setTimeout
nextTick 函数
nextTick 函数。接收两个参数:
- cb 回调函数:是要耽误实行的函数;
- ctx:指定 cb 回调函数 的 this 指向;
Vue 实例要领 $nextTick 做了进一步封装,把 ctx 设置为当前 Vue 实例。
export function nextTick(cb?: Function, ctx?: Object) {
let _resolve;
// cb 回调函数会经一致处置惩罚压入 callbacks 数组
callbacks.push(() => {
if (cb) {
// 给 cb 回调函数实行加上了 try-catch 错误处置惩罚
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
} else if (_resolve) {
_resolve(ctx);
}
});
// 实行异步耽误函数 timerFunc
if (!pending) {
pending = true;
timerFunc();
}
// 当 nextTick 没有传入函数参数的时刻,返回一个 Promise 化的挪用
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve;
});
}
}
小结
团体看过来,觉得照样比较好明白的吧~ 2.6 版本比拟之前简化了一点。
小结一下,每次挪用 Vue.nextTick(cb)
会做些什么:
cb 函数经处置惩罚压入 callbacks 数组,实行 timerFunc 函数,耽误挪用 flushCallbacks 函数,遍历实行 callbacks 数组中的一切函数。
耽误挪用优先级以下:
Promise > MutationObserver > setImmediate > setTimeout
版本差别
实在 Vue 2.4、2.5、2.6 版本的 nextTick 战略都略不一样。
团体 2.6 和 2.4 的比较类似。(细致瞅了瞅,基础就是一样的,2.6 timerFunc 多了个 setImmediate 推断)
2.5 版本实在也差不多。。。源码写法有些不一样,团体优先级是:Promise > setImmediate > MessageChannel > setTimeout,假如更新是在 v-on 事宜处置惩罚递次中触发的,nextTick 会优先运用 macrotask。感兴趣可自行去末端的参考地点举行查阅。
参考:
API—Vue.js
事宜轮回范例
Vue nextTick v2.6.7 源码
Vue nextTick v2.5.22 源码
Vue nextTick v2.4.4 源码
从 event loop 范例探讨 javaScript 异步及浏览器更新衬着机遇