In diesem Artikel wird hauptsächlich die Quellcode-Analyse der nextTick-Funktion in Vue vorgestellt. Der Herausgeber findet sie recht gut, daher werde ich sie jetzt mit Ihnen teilen und als Referenz verwenden. Folgen wir dem Herausgeber und schauen wir uns das an. Ich hoffe, es kann allen helfen.
1. Was ist Vue.nextTick()?
Die offizielle Dokumentation erklärt es wie folgt:
Ein verzögerter Rückruf, der nach dem Ende des nächsten DOM-Aktualisierungszyklus ausgeführt wird. Verwenden Sie diese Methode unmittelbar nach der Änderung der Daten, um das aktualisierte DOM zu erhalten.
2. Warum nextTick verwenden?
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <script src="https://tugenhua0707.github.io/vue/vue1/vue.js"></script> </head> <body> <p id="app"> <template> <p ref="list"> {{name}} </p> </template> </p> <script> new Vue({ el: '#app', data: { name: 'aa' }, mounted() { this.updateData(); }, methods: { updateData() { var self = this; this.name = 'bb'; console.log(this.$el.textContent); // aa this.$nextTick(function(){ console.log(self.$el.textContent); // bb }); } } }); </script> </body> </html>
Der obige Code zeigt bb in der Seitenansicht an, aber wenn ich wann Die Konsole druckt, der erhaltene Textinhalt ist immer noch aa, aber nach der Verwendung von nextTick ist der erhaltene Textinhalt der neueste Inhalt bb, sodass wir in diesem Fall die nextTick-Funktion verwenden können.
Warum ändert der obige Code this.name = 'bb'; und verwendet dann console.log(this.$el.textContent); um den Wert immer noch aa auszugeben? Das liegt daran, dass das DOM nach dem Festlegen des Werts von name nicht aktualisiert wurde, sodass der erhaltene Wert immer noch der vorherige Wert ist. Wenn wir ihn jedoch in die Funktion nextTick einfügen, wird der Code ausgeführt, nachdem das DOM aktualisiert wurde Das DOM wird aktualisiert und das Element wird erneut abgerufen. Der Wert kann verwendet werden, um den neuesten Wert zu erhalten.
DOM-Aktualisierungen verstehen: Wenn wir in VUE einen Wert in Daten ändern, werden die geänderten Daten nicht sofort in die asynchrone Warteschlange des Watchers, der Watcher-Warteschlangenaufgabe, übernommen wird nur ausgeführt, wenn die aktuelle Aufgabe inaktiv ist, was eine Verzögerungszeit hat, sodass nach dem Platzieren in der nextTick-Funktion der neueste Wert von el abgerufen werden kann. Es ist auch möglich, wenn wir den nextTick oben in setTimeout ändern.
3. Detaillierte Erklärung des Vue-Quellcodes nextTick (der Quellcode befindet sich in vue/src/core/util/env.js)
Bevor Sie die nextTick-Quelle verstehen Code, lassen Sie es uns zuerst verstehen. Das Folgende ist die neue MutationObserver-API in HTML5. Ihre Funktion besteht darin, DOM-Änderungen zu überwachen. Sie kann das Löschen von untergeordneten Knoten, Attributänderungen, Textinhaltsänderungen usw. eines DOM-Objekts überwachen.
Der NextTick-Quellcode lautet wie folgt:
export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc function nextTickHandler () { pending = false; /* 之所以要slice复制一份出来是因为有的cb执行过程中又会往callbacks中加入内容,比如$nextTick的回调函数里又有$nextTick, 那么这些应该放入到下一个轮次的nextTick去执行,所以拷贝一份,遍历完成即可,防止一直循环下去。 */ const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } } // the nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore if */ /* nextTick行为利用了microtask队列, 先使用 Promise.resolve().then(nextTickHandler)来将异步回调 放入到microtask中,Promise 和 MutationObserver都可以使用,但是 MutationObserver 在IOS9.3以上的 WebView中有bug,因此如果满足第一项的话就可以执行,如果没有原生Promise就用 MutationObserver。 */ if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) // in problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } } else if (typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // use MutationObserver where native Promise is not available, // e.g. PhantomJS IE11, iOS7, Android 4.4 /* 创建一个MutationObserver,observe监听到DOM改动之后执行的回调 nextTickHandler */ var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)); // 使用MutationObserver的接口,监听文本节点的字符内容 observer.observe(textNode, { characterData: true }); /* 每次执行timerFunc函数都会让文本节点的内容在0/1之间切换,切换之后将新赋值到那个我们MutationObserver监听的文本节点上去。 */ timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* 如果上面的两种都不支持的话,我们就使用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) } }); /* 如果pending为true,表明本轮事件循环中已经执行过 timerFunc(nextTickHandler, 0) */ if (!pending) { pending = true timerFunc() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })()
Verständnis der Gesamtidee: Erstens ist nextTick eine Abschlussfunktion und die Code wird sofort ausgeführt. Bevor wir uns den Code ansehen, schauen wir uns eine ähnliche Demo an, wie unten gezeigt:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> </head> <body> <p id="app"> </p> <script> var nextTick = (function(){ return function queueNextTick(cb, ctx) { if (cb) { try { cb.call(ctx) } catch (e) { console.log('出错了'); } } } })(); // 方法调用 nextTick(function(){ console.log(2); // 打印2 }) </script> </body> </html>
Der Democode ist dem obigen Code sehr ähnlich.
Wir können nextTick auch wie folgt extrahieren und verwenden, um Democode zu erstellen:
var nextTick2 = (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 < copies.length; i++) { copies[i]() } } if (typeof Promise !== 'undefined') { var p = Promise.resolve() var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError) } } else if (typeof MutationObserver !== 'undefined' || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' ) { // use MutationObserver where native Promise is not available, // e.g. PhantomJS IE11, iOS7, Android 4.4 var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } } return function queueNextTick (cb, ctx) { 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() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } } })(); nextTick2(function(){ console.log(2222); });
Der obige Code wird aus der nextTick-Quelle extrahiert Um nextTick besser zu verstehen, habe ich die obige Demo erstellt.
Lassen Sie uns die Bedeutung des gesamten Codes verstehen
Definieren Sie zunächst das Array callbacks = [], um alle auszuführenden Rückruffunktionen zu speichern, und definieren Sie let pending = false ; Bestimmen Sie, ob die Funktion timerFunc(nextTickHandler, 0) in dieser Ereignisrunde ausgeführt wurde. Wenn dies der Fall ist, bedeutet dies, dass die Funktion timeFunc ausgeführt wurde um die in den Array-Callbacks gespeicherten Funktionen nacheinander zu durchlaufen und nacheinander auszuführen; 🎜>Dann gibt es drei Beurteilungen, der Code lautet wie folgt:
Bestimmen Sie zunächst, ob das Promise-Objekt unterstützt wird. Wenn ja, definieren Sie die Funktion timeFunc() Um sich auf den nächsten Aufruf vorzubereiten, ermitteln Sie weiterhin, ob das Objekt MutationObserver unterstützt wird. Wenn sich die Knotendaten ändern, wird der Zählerwert zwischen 0 und 1 geändert /1. Wenn sich der Wert ändert, weisen Sie dem Datenattribut den Datenwert zu. Die Seite wird neu gerendert (da Vue überwacht, ob sich der Eigenschaftswert über Object.defineProperty ändert). Wenn die Bedingungen nicht erfüllt sind, verwenden Sie setTimeout direkt, um die nextTickHandler-Funktion auszuführen
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
Schließlich gibt der nextTick-Code eine Funktion zurück, der Code lautet wie folgt:
if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve(); var logError = err => { console.error(err) } timerFunc = () => { p.then(nextTickHandler).catch(logError); } else if (typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )){ var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { timerFunc = () => { setTimeout(nextTickHandler, 0) } }
Die Bedeutung des Codes ist: ob der eingehende cb eine Funktion ist, ob der ctx-Parameter ein Objekt ist, wenn cb eine Funktion ist, verwenden Sie cb.call(ctx), wenn timerFunc nicht ausgeführt wurde , dann ist pending false, sodass die Funktion timerFunc() ausgeführt wird. Die Grundidee ist folgende.
Verwandte Empfehlungen:
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() } if (!cb && typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { _resolve = resolve }) } }
Differenzanalyse des Timers nextTick() und setImmediate() in node.js_node.js
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Quellcodes der nextTick-Funktion in Vue. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!