Open Cosen95 opened 4 years ago
Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run( Watcher 对象的一个方法,用来触发 patch 操作) 一遍。
Vue.js
setter
Watcher
push
queue
tick
run
patch
因为目前浏览器平台并没有实现 nextTick 方法,所以 Vue.js 源码中分别用 Promise、setTimeout、setImmediate 等方式在 microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。
nextTick
Promise
setTimeout
setImmediate
microtask
task
nextTick方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用nextTick 会将方法存入队列中,通过这个异步方法清空当前队列。
所以这个 nextTick 方法是异步方法。
通过一张图来看下nextTick的实现:
cb
src/core/util/next-tick.js
export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
callbacks
timerFunc
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) { timerFunc = () => { // ... } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) {
timerFunc = () => { // ... } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } }
来看下`timerFunc`的取值逻辑: 1、 我们知道异步任务有两种,其中 `microtask` 要优于 `macrotask` ,所以优先选择 `Promise` 。因此这里先判断浏览器是否支持 `Promise`。 2、 如果不支持再考虑 `macrotask` 。对于 `macrotask` 会先后判断浏览器是否支持 `MutationObserver` 和 `setImmediate` 。 3、 如果都不支持就只能使用 `setTimeout` 。这也从侧面展示出了 `macrotask` 中 `setTimeout` 的性能是最差的。 > `nextTick`中 `if (!pending)` 语句中 `pending` 作用显然是让 `if` 语句的逻辑只执行一次,而它其实就代表 `callbacks` 中是否有事件在等待执行。 这里的`flushCallbacks`函数的主要逻辑就是将 `pending` 置为 `false` 以及清空 `callbacks` 数组,然后遍历 `callbacks` 数组,执行里面的每一个函数。 * `nextTick`的最后一步对应: ```js if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) }
这里 if 对应的情况是我们调用 nextTick 函数时没有传入回调函数并且浏览器支持 Promise ,那么就会返回一个 Promise 实例,并且将 resolve 赋值给 _resolve。回到nextTick开头的一段代码:
if
resolve
_resolve
let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } })
当我们执行 callbacks 的函数时,发现没有 cb 而有 _resolve 时就会执行之前返回的 Promise 对象的 resolve 函数。
Vue.js
在默认情况下,每次触发某个数据的setter
方法后,对应的Watcher
对象其实会被push
进一个队列queue
中,在下一个tick
的时候将这个队列queue
全部拿出来run
(Watcher
对象的一个方法,用来触发patch
操作) 一遍。因为目前浏览器平台并没有实现
nextTick
方法,所以Vue.js
源码中分别用Promise
、setTimeout
、setImmediate
等方式在microtask
(或是task
)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。nextTick
方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用nextTick
会将方法存入队列中,通过这个异步方法清空当前队列。通过一张图来看下
nextTick
的实现:nextTick
并传入cb
。对应源码src/core/util/next-tick.js
的87行。callbacks
数组用来存储nextTick
,在下一个tick
处理这些回调函数之前,所有的cb
都会被存在这个callbacks
数组中。timerFunc
函数。对应源码src/core/util/next-tick.js
的33行。if (typeof Promise !== 'undefined' && isNative(Promise)) { timerFunc = () => { // ... } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) {
timerFunc = () => { // ... } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } }
这里
if
对应的情况是我们调用nextTick
函数时没有传入回调函数并且浏览器支持Promise
,那么就会返回一个Promise
实例,并且将resolve
赋值给_resolve
。回到nextTick
开头的一段代码:当我们执行
callbacks
的函数时,发现没有cb
而有_resolve
时就会执行之前返回的Promise
对象的resolve
函数。