ホームページ >ウェブフロントエンド >jsチュートリアル >VueのnextTick関数のソースコードの詳細説明

VueのnextTick関数のソースコードの詳細説明

小云云
小云云オリジナル
2018-01-15 16:53:151586ブラウズ

この記事では主に 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: &#39;#app&#39;,
  data: {
   name: &#39;aa&#39;
  },
  mounted() {
   this.updateData();
  },
  methods: {
   updateData() {
   var self = this;
   this.name = &#39;bb&#39;;
   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 !== &#39;undefined&#39; && isNative(Promise)) {
 var p = Promise.resolve()
 var logError = err => { console.error(err) }
 timerFunc = () => {
  p.then(nextTickHandler).catch(logError)
  // in problematic UIWebViews, Promise.then doesn&#39;t completely break, but
  // it can get stuck in a weird state where callbacks are pushed into the
  // microtask queue but the queue isn&#39;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 !== &#39;undefined&#39; && (
 isNative(MutationObserver) ||
 // PhantomJS and iOS 7.x
 MutationObserver.toString() === &#39;[object MutationObserverConstructor]&#39;
 )) {
 // 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, &#39;nextTick&#39;)
  }
  } else if (_resolve) {
  _resolve(ctx)
  }
 });
 /* 如果pending为true,表明本轮事件循环中已经执行过 timerFunc(nextTickHandler, 0) */
 if (!pending) {
  pending = true
  timerFunc()
 }
 if (!cb && typeof Promise !== &#39;undefined&#39;) {
  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(&#39;出错了&#39;);
   }
   }
  }
  })();

  // 方法调用
  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 !== &#39;undefined&#39;) {
 var p = Promise.resolve()
 var logError = err => { console.error(err) }
 timerFunc = () => {
  p.then(nextTickHandler).catch(logError)
 }
 } else if (typeof MutationObserver !== &#39;undefined&#39; ||
 // PhantomJS and iOS 7.x
 MutationObserver.toString() === &#39;[object MutationObserverConstructor]&#39;
 ) {
 // 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, &#39;nextTick&#39;)
  }
  } else if (_resolve) {
  _resolve(ctx)
  }
 })
 if (!pending) {
  pending = true
  timerFunc()
 }
 if (!cb && typeof Promise !== &#39;undefined&#39;) {
  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 !== &#39;undefined&#39; && isNative(Promise)) {
 var p = Promise.resolve();
 var logError = err => { console.error(err) }
 timerFunc = () => {
 p.then(nextTickHandler).catch(logError);
} else if (typeof MutationObserver !== &#39;undefined&#39; && (
 isNative(MutationObserver) ||
 // PhantomJS and iOS 7.x
 MutationObserver.toString() === &#39;[object MutationObserverConstructor]&#39;
)){
 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, &#39;nextTick&#39;)
  }
 } else if (_resolve) {
  _resolve(ctx)
 }
 })
 if (!pending) {
 pending = true
 timerFunc()
 }
 if (!cb && typeof Promise !== &#39;undefined&#39;) {
 return new Promise((resolve, reject) => {
  _resolve = resolve
 })
 }
}

コードの意味は次のとおりです。受信した cb が関数であるかどうか、ctx パラメータがオブジェクトであるかどうか、cb が関数の場合は cb.call (ctx) を使用し、timerFunc が実行されていない場合は、pending は false です, したがって、timerFunc() 関数が実行されます。基本的な考え方はこれです。

関連する推奨事項:


Vue.nextTick実装方法の簡単な分析


Node.jsのprocess.nextTickの例を使用する

node.jsのTimer nextTick()とsetImmediate() 差分分析_node。 js

以上がVueのnextTick関数のソースコードの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。