>웹 프론트엔드 >JS 튜토리얼 >Vue의 nextTick 함수 소스 코드에 대한 자세한 설명

Vue의 nextTick 함수 소스 코드에 대한 자세한 설명

小云云
小云云원래의
2018-01-15 16:53:151585검색

이 글은 주로 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);를 사용하여 여전히 aa 값을 인쇄하는 이유는 무엇입니까? 왜냐하면 name 값을 설정한 후 DOM이 업데이트되지 않았기 때문에 얻은 ​​값은 여전히 ​​이전 값이지만, nextTick 함수에 넣으면 DOM이 업데이트된 후에 코드가 실행되기 때문입니다. DOM이 업데이트되면 최신 값을 다시 얻을 수 있습니다.

DOM 업데이트 이해: VUE에서는 데이터의 값을 수정하면 즉시 반영되지 않습니다. 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 = []을 정의하고, 이번 라운드의 이벤트가 실행되었는지 확인합니다. 실행됨 timeFunc(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]()
 }
}

그 다음 세 가지 판단이 있습니다.


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 개체가 지원되는지 여부를 계속 판단합니다. 지원되는 경우 노드 데이터가 변경되는지 모니터링합니다. 변경되면, counter 값은 0/1 사이에서 전환됩니다. 값이 변경되면 데이터 속성에 데이터 값을 할당하고, 데이터 속성이 변경되면 페이지가 다시 렌더링됩니다(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가 실행되지 않은 경우 보류 중입니다. false이므로 timeFunc() 함수가 실행됩니다. 기본 아이디어는 이것이다.


관련 권장 사항:


Vue.nextTick 구현 방법 간략 분석

Node.js의 process.nextTick 예시 사용

Timer nextTick() 및 node.js의 setImmediate() 차이점 분석_node. js

위 내용은 Vue의 nextTick 함수 소스 코드에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.