<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] # 為什么用Vue.nextTick() 首先來了解一下JS的運行機制。 <br> ## JS運行機制(Event Loop) JS執行是單線程的,它是基于事件循環的。 1. 所有同步任務都在主線程上執行,形成一個執行棧。 2. 主線程之外,會存在一個任務隊列,只要異步任務有了結果,就在任務隊列中放置一個事件。 3. 當執行棧中的所有同步任務執行完后,就會讀取任務隊列。那些對應的異步任務,會結束等待狀態,進入執行棧。 4. 主線程不斷重復第三步。 這里主線程的執行過程就是一個`tick`,而所有的異步結果都是通過任務隊列來調度。`Event Loop`分為宏任務和微任務,無論是執行宏任務還是微任務,完成后都會進入到一下`tick`,**并在兩個`tick`之間進行UI渲染**。 由于Vue DOM更新是異步執行的,即修改數據時,視圖不會立即更新,而是會監聽數據變化,并緩存在同一事件循環中,等同一數據循環中的所有數據變化完成之后,再統一進行視圖更新。為了確保得到更新后的DOM,所以設置了`Vue.nextTick()`方法。 <br> <br> # 什么是Vue.nextTick() 是Vue的核心方法之一,官方文檔解釋如下: > 在下次DOM更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的DOM。 <br> ## MutationObserver 先簡單介紹下`MutationObserver`:MO是HTML5中的API,是一個用于監視DOM變動的接口,它可以監聽一個DOM對象上發生的子節點刪除、屬性修改、文本內容修改等。 調用過程是要先給它綁定回調,得到MO實例,這個回調會在MO實例監聽到變動時觸發。這里MO的回調是放在`microtask`中執行的。 ~~~ // 創建MO實例 const observer = new MutationObserver(callback) const textNode = '想要監聽的Don節點' observer.observe(textNode, { characterData: true // 說明監聽文本內容的修改 }) ~~~ <br> ## 源碼淺析 `nextTick`的實現單獨有一個JS文件來維護它,在`src/core/util/next-tick.js`中。 `nextTick`源碼主要分為兩塊:能力檢測和根據能力檢測以不同方式執行回調隊列。 <br> ## 能力檢測 由于宏任務耗費的時間是大于微任務的,所以在瀏覽器支持的情況下,優先使用微任務。如果瀏覽器不支持微任務,再使用宏任務。 ~~~ // 空函數,可用作函數占位符 import { noop } from 'shared/util' // 錯誤處理函數 import { handleError } from './error' // 是否是IE、IOS、內置函數 import { isIE, isIOS, isNative } from './env' // 使用 MicroTask 的標識符,這里是因為火狐在<=53時 無法觸發微任務,在modules/events.js文件中引用進行安全排除 export let isUsingMicroTask = false // 用來存儲所有需要執行的回調函數 const callbacks = [] // 用來標志是否正在執行回調函數 let pending = false // 對callbacks進行遍歷,然后執行相應的回調函數 function flushCallbacks () { pending = false // 這里拷貝的原因是: // 有的cb 執行過程中又會往callbacks中加入內容 // 比如 $nextTick的回調函數里還有$nextTick // 后者的應該放到下一輪的nextTick 中執行 // 所以拷貝一份當前的,遍歷執行完當前的即可,避免無休止的執行下去 const copies = callbcks.slice(0) callbacks.length = 0 for(let i = 0; i < copies.length; i++) { copies[i]() } } let timerFunc // 異步執行函數 用于異步延遲調用 flushCallbacks 函數 // 在2.5中,我們使用(宏)任務(與微任務結合使用)。 // 然而,當狀態在重新繪制之前發生變化時,就會出現一些微妙的問題 // (例如#6813,out-in轉換)。 // 同樣,在事件處理程序中使用(宏)任務會導致一些奇怪的行為 // 因此,我們現在再次在任何地方使用微任務。 // 優先使用 Promise if(typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // IOS 的UIWebView, Promise.then 回調被推入 microTask 隊列,但是隊列可能不會如期執行 // 因此,添加一個空計時器強制執行 microTask if(isIOS) setTimeout(noop) } isUsingMicroTask = true } else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) { // 當 原生Promise 不可用時,使用 原生MutationObserver // e.g. PhantomJS, iOS7, Android 4.4 let counter = 1 // 創建MO實例,監聽到DOM變動后會執行回調flushCallbacks const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true // 設置true 表示觀察目標的改變 }) // 每次執行timerFunc 都會讓文本節點的內容在 0/1之間切換 // 切換之后將新值復制到 MO 觀測的文本節點上 // 節點內容變化會觸發回調 timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) // 觸發回調 } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { timerFunc = () => { setTimeout(flushCallbacks, 0) } } ~~~ 延遲調用優先級如下: Promise > MutationObserver > setImmediate > setTimeout ~~~ export function nextTick(cb? Function, ctx: Object) { let _resolve // cb 回調函數會統一處理壓入callbacks數組 callbacks.push(() => { if(cb) { try { cb.call(ctx) } catch(e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) // pending 為false 說明本輪事件循環中沒有執行過timerFunc() if(!pending) { pending = true timerFunc() } // 當不傳入 cb 參數時,提供一個promise化的調用 // 如nextTick().then(() => {}) // 當_resolve執行時,就會跳轉到then邏輯中 if(!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } ~~~ `next-tick.js`對外暴露了`nextTick`這一個參數,所以每次調用`Vue.nextTick`時會執行: * 把傳入的回調函數`cb`壓入`callbacks`數組 * 執行`timerFunc`函數,延遲調用`flushCallbacks`函數 * 遍歷執行`callbacks`數組中的所有函數 這里的`callbacks`沒有直接在`nextTick`中執行回調函數的原因是保證在同一個`tick`內多次執行`nextTick`,不會開啟多個異步任務,而是把這些異步任務都壓成一個同步任務,在下一個`tick`執行完畢。 <br> ## 附加 `noop`的定義如下 ~~~ /** * Perform no operation. * Stubbing args to make Flow happy without leaving useless transpiled code * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). */ export function noop (a?: any, b?: any, c?: any) {} ~~~ <br> <br> # 怎么用 **語法**:`Vue.nextTick([callback, context])` **參數**: * `{Function} [callback]`:回調函數,不傳時提供promise調用 * `{Object} [context]`:回調函數執行的上下文環境,不傳默認是自動綁定到調用它的實例上。 ~~~ //改變數據 vm.message = 'changed' //想要立即使用更新后的DOM。這樣不行,因為設置message后DOM還沒有更新 console.log(vm.$el.textContent) // 并不會得到'changed' //這樣可以,nextTick里面的代碼會在DOM更新后執行 Vue.nextTick(function(){ // DOM 更新了 //可以得到'changed' console.log(vm.$el.textContent) }) // 作為一個 Promise 使用 即不傳回調 Vue.nextTick() .then(function () { // DOM 更新了 }) ~~~ Vue實例方法`vm.$nextTick`做了進一步封裝,把context參數設置成當前Vue實例。 <br> <br> # 小結 使用`Vue.nextTick()`是為了可以獲取更新后的DOM 。 觸發時機:在同一事件循環中的數據變化后,DOM完成更新,立即執行`Vue.nextTick()`的回調。 > 同一事件循環中的代碼執行完畢 -> DOM 更新 -> nextTick callback觸發 ![![1596618069-5a5da8c8522c2_articlex](https://segmentfault.com/img/bVbya8q?w=423&h=512 "1596618069-5a5da8c8522c2_articlex")](images/screenshot_1627751757798.png) 應用場景: * 在Vue生命周期的`created()`鉤子函數進行的DOM操作一定要放在`Vue.nextTick()`的回調函數中。 **原因**:是`created()`鉤子函數執行時DOM其實并未進行渲染。 * 在數據變化后要執行的某個操作,而這個操作需要使用隨數據改變而改變的DOM結構的時候,這個操作應該放在`Vue.nextTick()`的回調函數中。 **原因**:Vue異步執行DOM更新,只要觀察到數據變化,Vue將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據改變,如果同一個watcher被多次觸發,只會被推入到隊列中一次。 <br> ## 版本分析 2.6 版本優先使用 microtask 作為異步延遲包裝器,且寫法相對簡單。而2.5 版本中,nextTick 的實現是 microTimerFunc、macroTimerFunc 組合實現的,延遲調用優先級是:Promise > setImmediate > MessageChannel > setTimeout,具體見源碼。 2.5 版本在重繪之前狀態改變時會有小問題(如[#6813](https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Fissues%2F6813))。此外,在事件處理程序中使用 macrotask 會導致一些無法規避的奇怪行為(如[#7109](https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Fissues%2F7109),[#7153](https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Fissues%2F7153)等)。 microtask 在某些情況下也是會有問題的,因為 microtask 優先級比較高,事件會在順序事件(如[#4521](https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Fissues%2F4521),[#6690](https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Fissues%2F6690)有變通方法)之間甚至在同一事件的冒泡過程中觸發([#6566](https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Fissues%2F6566))。 <br> <br> # 參考資料 [淺析Vue.nextTick()原理](https://segmentfault.com/a/1190000020499713)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看