이 글은 주로 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);를 사용하여 여전히 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 !== '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 = []을 정의하고, 이번 라운드의 이벤트가 실행되었는지 확인합니다. 실행됨 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 !== '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 개체가 지원되는지 여부를 계속 판단합니다. 지원되는 경우 노드 데이터가 변경되는지 모니터링합니다. 변경되면, 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, '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 함수 소스 코드에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!