Dieser Artikel wird mit Ihnen VueReine Informationen teilen und Ihnen Vue.nextTick vorstellen, die Sie nicht kennen. Ich hoffe, dass es für alle hilfreich ist!
Freunde, die Vue mehr oder weniger verwendet haben, kennen
$nextTick
~ Bevor Sie nextTick offiziell erklären, sollten Sie meiner Meinung nach klar wissen, dass Vue bei der Aktualisierungasynchron
ist DOM. Code>, da der nächste Erklärungsprozess zusammen mit Komponentenaktualisierungen erläutert wird ~ Kommen wir ohne weitere Umschweife direkt zum Thema (in diesem Artikel wird der Vue-Quellcode der Version v2.6.14$nextTick
~ 在正式讲解nextTick之前,我想你应该清楚知道 Vue 在更新 DOM 时是异步
执行的,因为接下来讲解过程会结合组件更新一起讲~ 事不宜迟,我们直进主题吧(本文以v2.6.14版本的Vue源码进行讲解)【相关推荐:vuejs视频教程】
你真的了解nextTick吗?来,直接上题~
<template> <div id="app"> <p ref="name">{{ name }}</p> <button @click="handleClick">修改name</button> </div> </template> <script> export default { name: 'App', data () { return { name: '井柏然' } }, mounted() { console.log('mounted', this.$refs.name.innerText) }, methods: { handleClick () { this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText)) } } } </script>
请问上述代码中,当点击按钮“修改name”时,'nextTick1'
,'sync log'
,'nextTick2'
对应的this.$refs.name.innerText
分别会输出什么?注意,这里打印的是DOM的innerText~(文章结尾处会贴出答案)
如果此时的你有非常坚定的答案,那你可以不用继续往下看了~但如果你对自己的答案有所顾虑,那不如跟着我,接着往下看。相信你看完,不需要看到答案都能有个肯定的答案了~!
源码位于core/util/next-tick中。可以将其分为4个部分来看,直接上代码
callbacks
队列、pending
状态
const callbacks = [] // 存放cb的队列 let pending = false // 是否马上遍历队列,执行cb的标志
flushCallbacks
遍历callbacks执行每个cb
function flushCallbacks () { pending = false // 注意这里,一旦执行,pending马上被重置为false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() // 执行每个cb } }
nextTick
的异步实现根据执行环境的支持程度采用不同的异步实现策略
let timerFunc // nextTick异步实现fn if (typeof Promise !== 'undefined' && isNative(Promise)) { // Promise方案 const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 将flushCallbacks包装进Promise.then中 } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // MutationObserver方案 let counter = 1 const observer = new MutationObserver(flushCallbacks) // 将flushCallbacks作为观测变化的cb const textNode = document.createTextNode(String(counter)) // 创建文本节点 // 观测文本节点变化 observer.observe(textNode, { characterData: true }) // timerFunc改变文本节点的data,以触发观测的回调flushCallbacks timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // setImmediate方案 timerFunc = () => { setImmediate(flushCallbacks) } } else { // 最终降级方案setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0) } }
MutationObserver
的理解。毕竟比起其他三种异步方案,这个应该是大家最陌生的const observer = new MutationObserver(() => console.log('观测到文本节点变化')) const textNode = document.createTextNode(String(1)) observer.observe(textNode, { characterData: true }) console.log('script start') setTimeout(() => console.log('timeout1')) textNode.data = String(2) // 这里对文本节点进行值的修改 console.log('script end')
script start
、script end
会在第一轮宏任务中执行,这点没问题
setTimeout
会被放入下一轮宏任务执行
MutationObserver
是微任务,所以会在本轮宏任务后执行,所以先于setTimeout
nextTick
方法实现cb
、Promise
方式
export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 往全局的callbacks队列中添加cb callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { // 这里是支持Promise的写法 _resolve(ctx) } }) if (!pending) { pending = true // 执行timerFunc,在下一个Tick中执行callbacks中的所有cb timerFunc() } // 对Promise的实现,这也是我们使用时可以写成nextTick.then的原因 if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
pending
有什么用,如何运作?案例1,同一轮Tick中执行2次$nextTick
,timerFunc
只会被执行一次
this.$nextTick(() => console.log('nextTick1')) this.$nextTick(() => console.log('nextTick2'))
这里如果有对Vue组件化、派发更新不是十分了解的朋友,可以先戳这里,看图解Vue响应式原理了解下Vue组件化和派发更新的相关内容再回来看噢~
Vue的异步更新DOM其实也是使用nextTick
来实现的,跟我们平时使用的$nextTick其实是同一个~
这里我们回顾一下,当我们改变一个属性值的时候会发生什么?
根据上图派发更新过程,我们从watcher.update开时讲起,以渲染Watcher为例,进入到queueWatcher
里
queueWatcher
做了什么?// 用来存放Wathcer的队列。注意,不要跟nextTick的callbacks搞混了,都是队列,但用处不同~ const queue: Array<Watcher> = [] function queueWatcher (watcher: Watcher) { const id = watcher.id // 拿到Wathcer的id,这个id每个watcher都有且全局唯一 if (has[id] == null) { // 避免添加重复wathcer,这也是异步渲染的优化做法 has[id] = true if (!flushing) { queue.push(watcher) } if (!waiting) { waiting = true // 这里把flushSchedulerQueue推进nextTick的callbacks队列中 nextTick(flushSchedulerQueue) } } }
flushSchedulerQueue
zur Erklärung verwendet) [ Verwandte Empfehlungen: vuejs Video-Tutorial】function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // 排序保证先父后子执行更新,保证userWatcher在渲染Watcher前 queue.sort((a, b) => a.id - b.id) // 遍历所有的需要派发更新的Watcher执行更新 for (index = 0; index < queue.length; index++) { watcher = queue[index] id = watcher.id has[id] = null // 真正执行派发更新,render -> update -> patch watcher.run() } }
-Schaltfläche „Name ändern“ geklickt wird, 'nextTick1'
, 'sync log'</ code>, Was wird durch den entsprechenden <code>this.$refs.name.innerText
von 'nextTick2'
ausgegeben? Hinweis: Was hier gedruckt wird, ist der innere Text des DOM~ (die Antwort wird am Ende des Artikels veröffentlicht)
Rückrufe
-Warteschlange, ausstehend
Status 🎜this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))
flushCallbacks
🎜🎜🎜Traverse Callbacks, um jeden cb auszuführen🎜rrreee🎜nextTick
🎜🎜🎜Verschiedene asynchrone Implementierungsstrategien entsprechend der Unterstützungsstufe der Ausführungsumgebung anwenden🎜rrreee🎜🎜Hier ist ein echter Fall Vertiefen Sie das Verständnis von MutationObserver
. Im Vergleich zu den anderen drei asynchronen Lösungen dürfte diese für jeden die unbekannteste sein. Wissen Sie, wie die entsprechende Ausgabe aussehen wird? script start
, script end
werden in der ersten Runde der Makroaufgaben ausgeführt, das ist nein Problem 🎜🎜🎜🎜setTimeout
wird in die nächste Runde der Makroaufgabenausführung eingefügt 🎜🎜🎜🎜MutationObserver
ist eine Mikroaufgabe, daher wird sie nach dieser Runde ausgeführt Makroaufgabe, also zuerst In setTimeout
🎜🎜🎜🎜🎜 ist das Ergebnis wie folgt:nextTick< /code>-Methodenimplementierung🎜🎜🎜 <code>cb
, Promise
-Methoden🎜rrreee🎜🎜Gehen Sie in die Details und verstehen Sie, wofür pending
verwendet wird und wie es funktioniert? 🎜🎜🎜Fall 1: $nextTick
wird zweimal in derselben Tick-Runde ausgeführt und timerFunc
wird nur einmal ausgeführtnextTick
implementiert, was eigentlich derselbe $nextTick ist, den wir normalerweise verwenden~🎜 🎜Hier überprüfen wir , was passiert, wenn wir den Wert eines Attributs ändern? 🎜🎜🎜🎜Gemäß In der obigen Abbildung beginnen wir mit dem Versandaktualisierungsprozess, wenn watcher.update geöffnet wird. Geben Sie am Beispiel von Rendering Watcher queueWatcher
🎜queueWatcher
? 🎜🎜rrreeeflushSchedulerQueue
? 🎜🎜rrreee🎜🎜Zum Schluss noch ein Bild zum Verständnis des asynchronen Aktualisierungsprozesses der Komponente🎜🎜🎜🎜🎜🎜🎜 Viertens zurück zur Frage selbst🎜🎜Ich glaube, dass wir nach der obigen Analyse des NextTick-Quellcodes dessen enthüllt haben geheimnisvoller Schleier. Zu diesem Zeitpunkt müssen Sie in der Lage sein, die Antwort ohne weiteres klar zu sagen. Lassen Sie uns gemeinsam prüfen, ob es das ist, was Sie denken! 🎜1、如图所示,mounted
时候的innerText是“井柏然”的中文
2、接下来是点击按钮后,打印结果如图所示
没错,输出结果如下(意不意外?惊不惊喜?)
sync log 井柏然
nextTick1 井柏然
nextTick2 jngboran
下面简单分析一下每个输出:
this.$nextTick(() => console.log('nextTick1', this.$refs.name.innerText)) this.name = 'jngboran' console.log('sync log', this.$refs.name.innerText) this.$nextTick(() => console.log('nextTick2', this.$refs.name.innerText))
sync log
:这个同步打印没什么好说了,相信大部分童鞋的疑问点都不在这里。如果不清楚的童鞋可以先回顾一下EventLoop,这里不多赘述了~
nextTick1
:注意其虽然是放在$nextTick
的回调中,在下一个tick执行,但是他的位置是在this.name = 'jngboran'
的前。也就是说,他的cb会比App组件的派发更新(flushSchedulerQueue
)更先进入队列,当nextTick1
打印时,App组件还未派发更新,所以拿到的还是旧的DOM值。
nextTick2
就不展开了,大家可以自行分析一下。相信大家对它应该是最肯定的,我们平时不就是这样拿到更新后的DOM吗?
最后来一张图加深理解
写在最后,nextTick其实在Vue中也算是比较核心的一个东西了。因为贯穿整个Vue应用的组件化、响应式的派发更新与其息息相关~深入理解nextTick的背后实现原理,不仅能让你在面试的时候一展风采,更能让你在日常开发工作中,少走弯路少踩坑!好了,本文到这里就暂告一段落了,如果读完能让你有所收获,就帮忙点个赞吧~画图不易、创作艰辛鸭~
Das obige ist der detaillierte Inhalt vonTeilen nützlicher Informationen, die Ihnen helfen, Vue.nextTick in Vue zu verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!