解析$nextTick魔力,為啥大家都愛它?

来源:https://www.cnblogs.com/Jcloud/archive/2023/12/20/17916769.html
-Advertisement-
Play Games

由於vue的試圖渲染是非同步的,生命周期的created()鉤子函數進行的DOM操作一定要放在Vue.nextTick()的回調函數中,原因是在created()鉤子函數執行的時候DOM其實並未進行渲染,而此時進行DOM操作是徒勞的,所以一定要將DOM操作的js代碼放到Vue.nextTick()的回... ...


1.為什麼需要使用$nextTick?

首先我們來看看官方對於$nextTick的定義:

在下次 DOM 更新迴圈結束之後執行延遲回調。在修改數據之後立即使用這個方法,獲取更新後的 DOM。

由於vue的試圖渲染是非同步的,生命周期的created()鉤子函數進行的DOM操作一定要放在Vue.nextTick()的回調函數中,原因是在created()鉤子函數執行的時候DOM其實並未進行渲染,而此時進行DOM操作是徒勞的,所以一定要將DOM操作的js代碼放到Vue.nextTick()的回調函數中。除了在created()鉤子函數中使用之外咱們還會遇到很多種需要使用到Vue.nextTick()的場景,如下所示:

咱們日常生活中常常會遇上上述場景,當我們點擊按鈕更新數據時候,如下示例:

<template>
    <div>
     <input type="text" v-if = "isShow" ref="input"/>
     <button @click="handleClick">點擊顯示輸入框,並且獲取輸入框焦點</button>
   </div>
</template>
<script>
export default {
 data() {
     return {
         isShow: false 
      }
 },
 methods : {
 handleClick () {
     this.isShow = true
     this.$refs.input.focus() //控制欄會報錯,因為還沒有這個dom    
    }
  }
}
</script>

點擊控制欄顯示效果:控制欄報錯,提示沒有獲取到dom元素;

所以現在Vue.nextTick()派上了用場,Vue.nextTick() 方法的作用正是等待上一次事件迴圈執行完畢,併在下一次事件迴圈開始時再執行回調函數。這樣可以保證回調函數中的 DOM 操作已經被 Vue.js 進行過更新,從而避免了一些潛在的問題,如下代碼所示:

<template>
  <div>
    <input type="text" v-if = "isShow" ref="input"/>
    <button @click="handleClick">點擊顯示輸入框,並且獲取輸入框焦點</button>
  </div>
</template>
<script>
export default {
 data() {
   return {
     isShow: false
   }
 },
 methods : {
   handleClick () {
     this.isShow = true
     this.$nextTick(()=>{
       this.$refs.input.focus() 
     })
 
   }
 }
}
</script>

加上this.$nextTick後就能夠使得輸入框獲取到焦點;

總而言之Vue.nextTick()就是下次 DOM 更新渲染後執行延遲回調函數。在日常開發中,我們在修改數據之後使用這個方法,就可以獲取更新後的 DOM的同時進行在對DOM進行相對應操作的 js代碼;

2.$nextTick如何實現的?

JS是單線程執行的,所有的同步任務都是在主線程上執行的,形成了一個執行棧,從上到下依次執行,非同步代碼會放在任務隊列裡面。

同步任務

在主線程里執行,當瀏覽器第一遍過濾html文件的時候可以執行完;(在當前作用域直接執行的所有內容,包括執行的方法、new出來的對象)

非同步任務

耗費時間較長或者性能較差的,瀏覽器執行到這些的時候會將其丟到非同步任務隊列中,不會立即執行

同時非同步任務分為巨集任務(如setTimeout、setInterval、postMessage、setImmediate等)和微任務(Promise、process.nextTick等),瀏覽器執行這兩種任務的優先順序不同;會優先執行微任務隊列的代碼,微任務隊列清空之後再執行巨集任務的隊列,這樣迴圈往複;

JS自上向下進行代碼的編譯執行,遇到同步代碼壓入JS執行棧執行後出棧,遇到非同步代碼放入任務隊列,當JS執行棧清空,去執行非同步隊列中的回調函數,先去執行微任務隊列,當微任務隊列清空後,去檢測執行巨集任務隊列中的回調函數,直至所有棧和隊列清空

整體流程如下圖所示:

接下來讓我們看看nextTick的源碼~

vue將nextTick的源碼放在了vue/core/util/next-tick.js中。如下圖所示:

我們把這個文件拆成三個部分來看:

1.nextTick定義函數

我們將nextTick函數單獨拿出來,callbacks是一個回調隊列,其實調用nextTick就是往這個數組裡面傳執行任務,callbacks新增回調函數之後執行timerFunc函數,pending是用來限制同一個事件迴圈內只能執行一次的pending鎖;

const callbacks = [] // 回調隊列
let pending = false // 
export function nextTick (cb?: Function, ctx?: Object) {
 let _resolve
 callbacks.push(() => {
  // cb 回調函數會經統一處理壓入 callbacks 數組
     if (cb) {
         try {
             cb.call(ctx)
         } catch (e) {
             handleError(e, ctx, 'nextTick')
         }
     } else if (_resolve) {
         _resolve(ctx)
        }
     })
  // 執行非同步延遲函數 timerFunc
     if (!pending) {
     pending = true
     timerFunc()
 }
 // $flow-disable-line
 // 當 nextTick 沒有傳入函數參數的時候,返回一個 Promise 化的調用
if (!cb && typeof Promise !== 'undefined') {
     return new Promise(resolve => {
     _resolve = resolve
     })
 }
}

2.timerFunc函數 做了四個判斷,先後嘗試當前環境是否能夠使用原生的Promise.then、MutationObserver和setImmediate,不斷的降級處理,如果以上三個都不支持,則最後就會直接使用setTimeOut,主要操作就是將flushCallbacks中的函數放入微任務或者巨集任務,等待下一個事件迴圈開始執行;巨集任務耗費的時間是大於微任務的,所以在瀏覽器支持的情況下,優先使用微任務。如果瀏覽器不支持微任務,使用巨集任務;但是,各種巨集任務之間也有效率的不同,需要根據瀏覽器的支持情況,使用不同的巨集任務;

export let isUsingMicroTask = false
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
 //是否支持Promise
 const p = Promise.resolve()
 timerFunc = () => {
 p.then(flushCallbacks)
  if (isIOS) setTimeout(noop)
 }
 isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
 isNative(MutationObserver) ||
 MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//是否支持MutationObserver 
 let counter = 1
 const observer = new MutationObserver(flushCallbacks)
 const textNode = document.createTextNode(String(counter))
 observer.observe(textNode, {
 characterData: true
 })
 timerFunc = () => {
 counter = (counter + 1) % 2
 textNode.data = String(counter)
 }
 isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
 timerFunc = () => {
  //是否支持setImmediate
 setImmediate(flushCallbacks)
 }
} else {
 // Fallback to setTimeout.
 timerFunc = () => {
  //上面都不行,直接使用setTimeout
 setTimeout(flushCallbacks, 0)
 }
}

3.flushCallbacks函數

flushCallbacks函數只有幾行,也很好理解,將pending鎖置為false,同時將callbacks數組複製一份之後再將callbacks置為空,接下來將複製出來的callbacks數組的每個函數依次進行執行,簡單來說它的主要作用就是用來執行callbacks中的回調函數;

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

值得註意的是,$nextTick 並不是一個真正意義上的微任務microtask,而是利用了事件迴圈機制來實現非同步更新。因此,它的執行時機相對於微任務可能會有所延遲,但仍能保證在 DOM 更新後儘快執行回調函數。

總的來說,nextTick就是

1.將傳入的回調函數放入callbacks數組等待執行,定義pending判斷鎖保證一個事件迴圈中只能調用一次timerFunc函數;

2.根據環境判斷使用非同步方式,調用timerFunc函數調用flushCallbacks函數依次執行callbacks中的回調函數;

3.個人小結

nextTick可避免數據更新後導致DOM的數據不一致的問題,提供了更穩定的非同步更新機制,解決了created鉤子函數DOM未渲染會造成的非同步數據渲染問題,但如果過多的使用nextTick會導致事件迴圈中任務數量和回調函數增多,有可能出現可怕的回調地獄,導致性能下降,同時過度依賴nextTick也會降低代碼的可讀性,所以大家還是"按需載入"的好~

作者:京東保險 卓雅倩

來源:京東雲開發者社區 轉載請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、按照月分片 使用場景為按照自然月來分片,每個自然月為一個分片,但是一年有12個月,是不是要有12個數據節點才行呢?並不是。例如我現在只有三個分片資料庫,這樣就可以1月在第一個數據分片中,2月在第二個數據分片中,3月在第三個數據分片中,當來到4月的時候,就會重新開始分片,4月在第一個數據分片,5月 ...
  • 1. 基礎知識回顧 1、索引的有序性,索引本身就是有序的 2、InnoDB中間隙鎖的唯一目的是防止其他事務插入間隙。間隙鎖可以共存。一個事務取得的間隙鎖並不會阻止另一個事務取得同一間隙上的間隙鎖。共用和獨占間隔鎖之間沒有區別。它們彼此之間不衝突,並且執行相同的功能。 3、MySQL預設隔離級別是 R ...
  • 芋道源碼相信很多朋友都很瞭解了,今天我們試著基於FastGPT實現芋道框架的代碼生成。芋道的代碼生成,是基於資料庫表欄位實現的,那我們的思路就是看看如何使用GPT幫我們生成資料庫表結構,只要資料庫表欄位有了,代碼也就生成好了。實現這個需求我們就需要用到FastGPT的高級編排功能。編排的整體思路如下 ...
  • 在之前三期的實時湖倉系列文章中,我們從業務側、產品側、應用側等幾個方向,為大家介紹了實時湖倉方方面面的內容,包括實時湖倉對於企業數字化佈局的重要性以及如何進行實時湖倉的落地實踐等。 本文將從純技術的角度,為大家解析實時湖倉的存儲原理以及生態選型,為企業建設實時湖倉給出技術方面的參考意見。 實時湖倉能 ...
  • 關於GreatSQL字元集的總結 前言 最近的SQL優化工作中經常遇到因字元集或校驗規則不一致導致索引使用不了的問題,修改表的字元集或校驗規則相當於把表重構,表中數據量大時,處理起來費時費力,希望應用開發者在設計之初時註意到此問題,讓後期接手運維的小伙伴少一些負擔。GreatSQL的字元集和校驗規則 ...
  • create database step2; go use step2; go ​ -- 學生表 create table StudentInfo ( stuId char(10) primary key, -- 主鍵 stuName varchar(20), -- 姓名 ClassId int, ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1、背景 與後端對介面時,看到有一個get請求的介面,它的參數是放在body中的 ******get請求參數可以放在body中?? 隨即問了後端,後端大哥說在postman上是可以的,還給我看了截圖 可我傳參怎麼也調不通! 下麵就來探究到 ...
  • 項目代碼同步至碼雲 weiz-vue3-template 基於 axios 封裝請求,支持多功能變數名稱請求地址 安裝 npm i axios 封裝 utils 目錄下新建 request 文件夾,並新建 index.ts、request.ts 和 status.ts 文件。 1. status.ts 文件 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...