Vue nextTickメカニズム
6975 ワード
背景
まず、Vueの実行コードを見てみましょう.
このスクリプトの実行では、1、2、3の順に印刷されると推測されます.しかし、実際の効果では、1回:3しか出力されません.どうしてこんなことになったの?真相を探ってみましょう.
queueWatcher
watchリスニングmsgを定義し、実際にはVueによってvm.$が呼び出されます.watch(keyOrFn, handler, options).$watchは、私たちが初期化したときにvmにバインドされた関数で、Watcherオブジェクトを作成します.では、Watcherでhandlerをどのように処理しているかを見てみましょう.
初期設定deep = this.user = this.lazy = this.sync=false、すなわちupdate更新がトリガーされるとqueueWatcherメソッドが実行されます.
このnextTick(flushSchedulerQueue)のflushSchedulerQueue関数は、watcherのビュー更新です.
まず、Vueの実行コードを見てみましょう.
export default { data () { return {
msg: 0
}
},
mounted () { this.msg = 1
this.msg = 2
this.msg = 3
},
watch: {
msg () {
console.log(this.msg)
}
}
}
このスクリプトの実行では、1、2、3の順に印刷されると推測されます.しかし、実際の効果では、1回:3しか出力されません.どうしてこんなことになったの?真相を探ってみましょう.
queueWatcher
watchリスニングmsgを定義し、実際にはVueによってvm.$が呼び出されます.watch(keyOrFn, handler, options).$watchは、私たちが初期化したときにvmにバインドされた関数で、Watcherオブジェクトを作成します.では、Watcherでhandlerをどのように処理しているかを見てみましょう.
this.deep = this.user = this.lazy = this.sync = false...
update () { if (this.lazy) { this.dirty = true
} else if (this.sync) { this.run()
} else {
queueWatcher(this)
}
}
...
初期設定deep = this.user = this.lazy = this.sync=false、すなわちupdate更新がトリガーされるとqueueWatcherメソッドが実行されます.
const queue: Array = []let has: { [key: number]: ?true } = {}let waiting = falselet flushing = false...export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else { // if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
} // queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}
このnextTick(flushSchedulerQueue)のflushSchedulerQueue関数は、watcherのビュー更新です.
function flushSchedulerQueue () {
flushing = true
let watcher, id
... for (index = 0; index
, waiting , , flushSchedulerQueue callbacks 。 nextTick , nexTick , Event Loop、microTask、macroTask ,Vue nextTick 。 , Event Loop , :
export const nextTick = (function () { const callbacks = [] let pending = false
let timerFunc function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i {
setImmediate(nextTickHandler)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) || // PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) { const channel = new MessageChannel() const port = channel.port2
channel.port1.onmessage = nextTickHandler
timerFunc = () => {
port.postMessage(1)
}
} else
/* istanbul ignore next */
if (typeof Promise !== 'undefined' && isNative(Promise)) { // use microtask in non-DOM environments, e.g. Weex
const p = Promise.resolve()
timerFunc = () => {
p.then(nextTickHandler)
}
} else { // fallback to setTimeout
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
} return function queueNextTick (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, reject) => {
_resolve = resolve
})
}
}
})()
まずVueはcallback によってイベントキューをシミュレートし,イベントキュー のイベントはnexttickHandlerメソッドによって び され, が されるかはtimerFuncによって される.timeFuncの を てみましょう. if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(nextTickHandler)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) || // PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) { const channel = new MessageChannel() const port = channel.port2
channel.port1.onmessage = nextTickHandler
timerFunc = () => {
port.postMessage(1)
}
} else
/* istanbul ignore next */
if (typeof Promise !== 'undefined' && isNative(Promise)) { // use microtask in non-DOM environments, e.g. Weex
const p = Promise.resolve()
timerFunc = () => {
p.then(nextTickHandler)
}
} else { // fallback to setTimeout
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
timerFuncの macroTask-->microTaskはDomのない ではweexのようなmicroTaskを いていることがわかる.
setImmediate、MessageChannel VS setTimeout
setImmediate、MessageChannelを に するのはなぜsetTimeoutではなくmacroTaskを に するのですか?HTML 5ではsettimeoutの は4 msと されているが, な では コールバックが も くても4 msでトリガできる.Vueは、コールバックを し、 に び すことを とする タスクをシミュレートするために、このような くの を します. 、MessageChannelとsetImmediateの はsetTimeoutより らかに さい.
を する
これらの があれば、 の をもう てみましょう.Vueのイベントメカニズムはイベントキューによって がスケジューリングされるため、メインプロセスが きを してからスケジューリングが われるので、すべてのプロセスの が してから します. のようなパフォーマンスのメリットがあります.
mountedの 、testの は++で1000 サイクル される があります.++のたびに、 に ってsetter->Dep->Watcher->update->runがトリガーされます.このとき でビューを しないと、++のたびにDOM ビューが され、パフォーマンスが に されます.したがって、Vueは、 のTick(または のTickのマイクロタスクフェーズ)のときにqueue のWatcherのrunを に するqueueキューを する. に、 じidを つWatcherはこのqueueに して されないので、1000 Watcherのrunは されません. ビューはtest DOMの0を 1000にするだけです.ビューの DOMを する は、 のスタックが された の のTick(または のTickのマイクロタスクフェーズ)のときに び され、パフォーマンスが に されます.
おもしろい var vm = new Vue({ el: '#example', data: { msg: 'begin',
},
mounted () { this.msg = 'end'
console.log('1')
setTimeout(() => { // macroTask
console.log('3')
}, 0) Promise.resolve().then(function () { //microTask
console.log('promise!')
}) this.$nextTick(function () { console.log('2')
})
}
})
この は、1、promise、2、3の で されていることを っていると います. にこれをトリガーしたからです.msg='end'は、watcherのupdateをトリガーし、 callback pushをvueのイベントキューに れます. this.$nextTickはまた、setImmediate-->MessageChannel-->Promise-->setTimeoutによってtimeFuncを するイベントキューpushの しいcallback に りました.そしてresolve().thenはmicroTaskなので、promiseを に します. MessageChannelとsetImmediateをサポートする 、 らの はsetTimeoutよりも される(IE 11/EdgeではsetImmediate は1 ms であり、setTimeoutでは 4 msの があるため、setImmediateはsetTimeout(0)よりも くコールバック を する. にイベントキューにはcallback が に されるので2が され、3 が される.ですが、MessageChannelとsetImmediateがサポートされていない は、PromiseでtimeFuncが され、 バージョンVue 2.4 のバージョンでもpromiseが されます.この ,1,2,promise,3の になる.なぜならmsgは ずdom をトリガし、dom はcallbackに されて タイムキューに り、 にPromiseを する.resolve().then(function(){console.log('promise!')})のようなmicroTaskは、$nextTickを してcallbackに されます.キューは の を たすことを っているのでcallback の を します.