この記事では主に Vue の nextTick 関数のソースコード解析を紹介していますが、編集者が非常に優れていると思ったので、参考として共有します。編集者をフォローして見てみましょう。皆さんのお役に立てれば幸いです。
1. Vue.nextTick() とは何ですか?
公式ドキュメントでは次のように説明されています:
次の DOM 更新サイクルが終了した後に実行される遅延コールバック。データを変更した直後にこのメソッドを使用して、更新された DOM を取得します。
2. nextTick を使用する理由
<!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>
上記のコードはページビューに bb を表示しますが、コンソールに印刷すると、取得されたテキストコンテンツはまだ aa ですが、nextTick を使用すると、取得されたテキスト コンテンツはまだ aa です。テキスト コンテンツは最新のコンテンツ bb であるため、この場合は nextTick 関数を使用できます。
なぜ上記のコードは this.name = 'bb'; を変更してから console.log(this.$el.textContent); を使用して値を出力するのでしょうか?それは、nameの値を設定した後、DOMが更新されていないため、取得された値は前の値のままですが、nextTick関数に入れると、DOMが更新された後にコードが実行されるためです。 DOM が更新されると、要素が再度取得され、最新の値が取得されます。
DOM の更新について理解します。VUE では、データ内の値を変更しても、それはすぐには el に反映されません。Vue は、現在のウォッチャー キュー タスク内でのみ、変更されたデータをウォッチャーの非同期キューに置きます。タスクがアイドル状態の場合にのみ実行されます。遅延時間があるため、nextTick関数に配置した後、elの最新の値を取得できます。上記の nextTick を setTimeout に変更しても可能です。
3. Vue ソースコード nextTick の詳細説明 (ソースコードは vue/src/core/util/env.js にあります)
nextTick ソースコードを理解する前に、まず html5 の新しい MutationObserver API を理解しましょう。 DOM の変更を監視するために使用されるインターフェースであり、DOM オブジェクトの子ノードの削除、属性の変更、テキスト内容の変更などを監視できます。
nextTick のソース コードは次のとおりです:
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 }) } } })()
全体的な概念の理解: まず、nextTick はクロージャー関数であり、コードはすぐに実行されます。全体的なコードを理解する前に、同様のコードを見てみましょう。デモは次のとおりです。
<!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>
デモ コードは上記のコードとよく似ています。
次のように nextTick を抽出して使用してデモ コードを作成することもできます:
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); });
上記のコードは nextTick のソース コードから抽出したものです。 nextTick をよりよく理解するために、上記のデモを作成しました。
コード全体の意味を理解しましょう。
まず、実行する必要があるすべてのコールバック関数を格納する配列 callbacks = []; を定義し、このラウンドのイベントが完了したかどうかを決定します。 completed timerFunc(nextTickHandler, 0) この関数の場合、true の場合、timeFunc 関数が実行され、nextTickHandler 関数が定義されていることを意味します。この関数の機能は、配列コールバックに保存された関数を順番に走査することです。
以下のソースコードを見てください:
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
そして、コードは次の 3 つの判断があります:
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) } }
まず、Promise オブジェクトがサポートされているかどうかを判断します。サポートされている場合は、次の呼び出しに備えて timeFunc() 関数が定義され、オブジェクト MutationObserver がサポートされているかどうかを判断し、テキスト ノードを作成し、ノード データが変更されるかどうかを監視します。 、 timerFunc 関数を呼び出します。値が変更された場合は、データ値を data 属性に割り当てます。データ属性が変更された場合は、ページが再レンダリングされます。属性値は Object.defineProperty によって変更されます)。上記の 2 つの状況が満たされない場合は、setTimeout を直接使用して nextTickHandler 関数を実行します。最後の nextTick コードは次のとおりです。
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 }) } }
Vue.nextTick実装方法の簡単な分析
Node.jsのprocess.nextTickの例を使用する
node.jsのTimer nextTick()とsetImmediate() 差分分析_node。 js
以上がVueのnextTick関数のソースコードの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。