Maison  >  Article  >  interface Web  >  Explication détaillée du code source de la fonction nextTick dans Vue

Explication détaillée du code source de la fonction nextTick dans Vue

小云云
小云云original
2018-01-15 16:53:151557parcourir

Cet article présente principalement l'analyse du code source de la fonction nextTick dans Vue. L'éditeur pense que c'est plutôt bon, je vais donc le partager avec vous maintenant et le donner comme référence. Suivons l'éditeur pour y jeter un œil, j'espère que cela pourra aider tout le monde.

1. Qu'est-ce que Vue.nextTick() ?

La documentation officielle l'explique comme suit :

Un rappel différé exécuté après la fin du prochain cycle de mise à jour du DOM. Utilisez cette méthode immédiatement après avoir modifié les données pour obtenir le DOM mis à jour.

2. Pourquoi utiliser 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>

Le code ci-dessus affiche bb sur la page vue, mais quand je Quand la console s'imprime, le contenu texte obtenu est toujours aa, mais après avoir utilisé nextTick, le contenu texte obtenu est le dernier contenu bb, donc dans ce cas, on peut utiliser la fonction nextTick.

Pourquoi le code ci-dessus change-t-il this.name = 'bb'; puis utilise console.log(this.$el.textContent); En effet, après avoir défini la valeur de name, le DOM n'a pas été mis à jour, donc la valeur obtenue est toujours la valeur précédente. Cependant, lorsque nous la mettons dans la fonction nextTick, le code sera exécuté après la mise à jour du DOM, donc après. le DOM est mis à jour, l'élément est à nouveau obtenu. La valeur peut être utilisée pour obtenir la dernière valeur.

Comprendre les mises à jour du DOM : dans VUE, lorsque nous modifions une valeur dans les données, cela ne sera pas immédiatement reflété dans l'el. Vue placera les données modifiées dans une file d'attente asynchrone de l'observateur, la tâche de file d'attente de l'observateur. ne sera exécuté que lorsque la tâche en cours est inactive, ce qui a un délai, donc après l'avoir placée dans la fonction nextTick, la dernière valeur de l'el peut être obtenue. C'est également possible si nous changeons le nextTick ci-dessus en setTimeout.

3. Explication détaillée du code source de Vue nextTick (le code source est dans vue/src/core/util/env.js)

Avant de comprendre le source de nextTick code, comprenons-le d'abord Ce qui suit est la nouvelle API MutationObserver en HTML5. Sa fonction est de surveiller les modifications du DOM. Elle peut surveiller la suppression des nœuds enfants, les modifications d'attributs, les modifications de contenu de texte, etc.

Le code source de nextTick est le suivant :


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
  })
 }
 }
})()

Compréhension de l'idée générale : Premièrement, nextTick est une fonction de fermeture, et le le code est exécuté immédiatement. Après avoir compris l'ensemble. Avant de regarder le code, jetons un coup d'œil à une démo similaire, comme suit :


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

Le code de la démo est très similaire au code ci-dessus.

Nous pouvons également extraire et utiliser nextTick pour créer du code de démonstration comme suit :


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);
});

Le code ci-dessus est extrait de la source nextTick code. Pour mieux comprendre nextTick, j'ai réalisé la démo ci-dessus.

Comprenons la signification du code global

Définissons d'abord le tableau callbacks = []; ending = false ; Déterminez si la fonction timerFunc(nextTickHandler, 0) a été exécutée dans cette série d'événements. Si c'est vrai, cela signifie que la fonction timeFunc a été exécutée. Ensuite, définissez la fonction nextTickHandler. pour parcourir les fonctions enregistrées dans les rappels du tableau dans l'ordre et les exécuter dans l'ordre

Veuillez consulter le code source comme suit :


function nextTickHandler () {
 pending = false
 const copies = callbacks.slice(0)
 callbacks.length = 0
 for (let i = 0; i < copies.length; i++) {
 copies[i]()
 }
}

Ensuite, il y a trois jugements, le code est le suivant :


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)
 }
}

Déterminez d'abord si l'objet Promise est pris en charge. Si c'est le cas, définissez la fonction timeFunc(). pour préparer le prochain appel. Continuez ensuite à déterminer si l'objet MutationObserver est pris en charge. Si c'est le cas, créez un nœud de texte si les données du nœud changent, appelez la fonction timerFunc et la valeur du compteur basculera entre 0. /1. Si la valeur change, attribuez la valeur des données à l'attribut data, alors l'attribut data a changé. La page sera restituée (car Vue surveille si la valeur de la propriété change via Object.defineProperty). les conditions ne sont pas satisfaites, puis utilisez setTimeout directement pour exécuter la fonction nextTickHandler

Enfin le code nextTick renvoie une fonction, le code est le suivant :


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
 })
 }
}

La signification du code est : si le cb entrant est une fonction, si le paramètre ctx est un objet, si Si cb est une fonction, utilisez cb.call(ctx). , alors ending est faux, donc la fonction timerFunc() est exécutée. L'idée de base est la suivante.

Recommandations associées :

Une brève analyse de la méthode d'implémentation du processus Vue.nextTick

dans Node Exemple d'utilisation de .js .nextTick

Analyse des différences entre timer nextTick() et setImmediate() dans node.js_node.js

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn