首頁  >  文章  >  web前端  >  Vue中nextTick函數原始碼詳解

Vue中nextTick函數原始碼詳解

小云云
小云云原創
2018-01-15 16:53:151542瀏覽

本文主要介紹了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後,取得的文字內容就是最新的內容bb了,因此在這種情況下,我們可以使用nextTick函數了。

上面的程式碼為什麼要改變this.name = 'bb';後,再使用console.log(this.$el.textContent);列印的值還是aa呢?那是因為設定name的值後,DOM還沒更新到,所以取得值還是之前的值,但是我們放到nextTick函數裡面的時候,程式碼會在DOM更新後執行,因此DOM更新後,再去取得元素的值就可以取得到最新值了。

瞭解DOM更新:在VUE中,當我們修改了data中的某一個值後,並不會立即反應到該el中,vue將對更改的資料放到watcher的一個非同步佇列中,只有在目前任務空閒時才會執行watcher佇列任務,這就有一個延遲時間,因此放到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 是一個閉包函數,程式碼立即執行,在理解整體程式碼之前,我們先來看個類似的demo,如下程式碼:


<!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>

demo程式碼和上面的程式碼很類似。

我們也可以再來抽離使用nextTick做demo程式碼如下:


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,做瞭如上的demo。

我們再來理解一下整體的程式碼的意義;

先定義數組callbacks = [];來存放所有需要執行的回呼函數,定義let pending = false ;判斷本輪事件是否執行過timerFunc(nextTickHandler, 0)這個函數,為true說明執行過timeFunc函數,接著定義nextTickHandler函數,該函數的作用是依序遍歷數組callbacks保存的函數,依序執行;

#請看原始程式碼如下:


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; && 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函數,counter值會在0/1切換,如果值改變了的話,把該資料值賦值到data屬性上面去,那麼data屬性改變了,就會重新渲染頁面(因為vue是透過Object.defineProperty來監聽屬性值是否改變),如果上面兩種情況都不滿足的話,那麼直接使用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中的定時器nextTick()和setImmediate()區別分析_node.js

以上是Vue中nextTick函數原始碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn