❶ 【面試題解析】從 Vue 源碼分析 key 的作用
最近看了面試題中有一個這樣的題, v-for 為什麼要綁定 key?
Vue 中 key 很多人都弄不清楚有什麼作用,甚至還有些人認為不綁定 key 就會報錯。
其實沒綁定 key 的話,Vue 還是可以正常運行的,報警告是因為沒通過 Eslint 的檢查。
接下來將通過源碼一步步分析這個 key 的作用。
Virtual DOM 最主要保留了 DOM 元素的層級關系和一些基本屬性,本質上就是一個 JS 對象。相對於真實的 DOM,Virtual DOM 更簡單,操作起來速度更快。
如果需要改變 DOM,則會通過新舊 Virtual DOM 對比,找出需要修改的節點進行真實的 DOM 操作,從而減小性能消耗。
傳統的 Diff 演算法需要遍歷一個樹的每個節點,與另一棵樹的每個節點對比,時間復雜度為 O(n²)。
Vue 採用的 Diff 演算法則通過逐級對比,大大降低了復雜性,時間復雜度為 O(n)。
VNode 更新首先會經過 patch 函數, patch 函數源碼如下:
vnode 表示更新後的節點,oldVnode 表示更新前的節點,通過對比新舊節點進行操作。
1、vnode 未定義,oldVnode 存在則觸發 destroy 的鉤子函數
2、oldVnode 未定義,則根據 vnode 創建新的元素
3、oldVnode 不為真實元素並且 oldVnode 與 vnode 為同一節點,則會調用 patchVnode 觸發更新
4、oldVnode 為真實元素或者 oldVnode 與 vnode 不是同一節點,另做處理
接下來會進入 patchVnode 函數,源碼如下:
1、vnode 的 text 不存在,則會比對 oldVnode 與 vnode 的 children 節點進行更新操作
2、vnode 的 text 存在,則會修改 DOM 節點的 text
接下來在 updateChildren 函數內就可以看到 key 的用處。
key 的作用主要是給 VNode 添加唯一標識,通過這個 key,可以更快找到新舊 VNode 的變化,從而進一步操作。
key 的作用主要表現在以下這段源碼中。
updateChildren 過程為:
1、分別用兩個指針(startIndex, endIndex)表示 oldCh 和 newCh 的頭尾節點
2、對指針所對應的節點做一個兩兩比較,判斷是否屬於同一節點
3、如果4種比較都沒有匹配,那麼判斷是否有 key,有 key 就會用 key 去做一個比較;無 key 則會通過遍歷的形式進行比較
4、比較的過程中,指針往中間靠,當有一個 startIndex > endIndex,則表示有一個已經遍歷完了,比較結束
從 VNode 的渲染過程可以得知,Vue 的 Diff 演算法先進行的是同級比較,然後再比較子節點。
子節點比較會通過 startIndex、endIndex 兩個指針進行兩兩比較,再通過 key 比對子節點。如果沒設置 key,則會通過遍歷的方式匹配節點,增加性能消耗。
所以不綁定 key 並不會有問題,綁定 key 之後在性能上有一定的提升。
綜上,key 主要是應用在 Diff 演算法中,作用是為了更快速定位出相同的新舊節點,盡量減少 DOM 的創建和銷毀的操作。
希望以上內容能夠對各位小夥伴有所幫助,祝大家面試順利。
Vue 的文檔中對 key 的說明如下:
關於就地修改,關鍵在於 sameVnode 的實現,源碼如下:
可以看出,當 key 未綁定時,主要通過元素的標簽等進行判斷,在 updateChildren 內會將 oldStartVnode 與 newStartVnode 判斷為同一節點。
如果 VNode 中只包含了文本節點,在 patchVnode 中可以直接替換文本節點,而不需要移動節點的位置,確實在不綁定 key 的情況下效率要高一丟丟。
某些情況下不綁定 key 的效率更高,那為什麼大部分Eslint的規則還是要求綁定 key 呢?
因為在實際項目中,大多數情況下 v-for 的節點內並不只有文本節點,那麼 VNode 的位元組點就要進行銷毀和創建的操作。
相比替換文本帶來的一丟丟提升,這部分會消耗更多的性能,得不償失。
了解了就地修改,那麼我們在一些簡單節點上可以選擇不綁定 key,從而提高性能。
如果你喜歡我的文章,希望可以關注一下我的公眾號【前端develop】
❷ 怎麼把vue的動態數據在查看源代碼中展示
Chrome或同內核的瀏覽器,使用開發者工具(f12)->network可以看到ajax請求回來的數據
❸ vue3源碼分析-實現props,emit,事件處理等
>
本期來實現, setup裡面使用props,父子組件通信props和emit等 ,所有的源碼請查看
在render函數中, 可以通過this,來訪問setup返回的內容,還可以訪問this.$el等
由於是測試dom,jest需要提前注入下面的內容,讓document裡面有app節點,下面測試用例類似在html中定義一個app節點哦
本功能的測試用例正式開始
上面的測試用例
解決這兩個需求:
針對上面的分析,需要在setupStatefulComponent中來創建proxy並且綁定到instance當中,並且setup的執行結果如果是對象,也已經存在instance中了,可以通過instance.setupState來進行獲取
通過上面的操作,從render中this.xxx獲取setup返回對象的內容就ok了,接下來處理el
需要在mountElement中,創建節點的時候,在vnode中綁定下,el,並且在setupStatefulComponent 中的代理對象中判斷當前的key
看似沒有問題吧,但是實際上是有問題的,請仔細思考一下, mountElement是不是比setupStatefulComponent 後執行,setupStatefulComponent執行的時候,vnode.el不存在,後續mountelement的時候,vnode就會有值,那麼上面的測試用例肯定是報錯的,$el為null
解決這個問題的關鍵,mountElement的載入順序是 render -> patch -> mountElement,並且render函數返回的subtree是一個vnode,改vnode中上面是mount的時候,已經賦值好了el,所以在patch後執行下操作
在vue中,可以使用onEvent來寫事件,那麼這個功能是怎麼實現的呢,咋們一起來看看
在本功能的測試用例中,可以分析以下內容:
解決問題:
這個功能比較簡單,在處理prop中做個判斷, 屬性是否滿足 /^on[A-Z]/i這個格式,如果是這個格式,則進行事件注冊,但是vue3會做事件緩存,這個是怎麼做到?
緩存也好實現,在傳入當前的el中增加一個屬性 el._vei || (el._vei = {}) 存在這里,則直接使用,不能存在則創建並且存入緩存
事件處理就ok啦
父子組件通信,在vue中是非常常見的,這里主要實現props與emit
根據上面的測試用例,分析props的以下內容:
解決問題:
問題1: 想要在子組件的setup函數中第一個參數, 使用props,那麼在setup函數調用的時候,把當前組件的props傳入到setup函數中即可 問題2: render中this想要問題,則在上面的那個代理中,在 加入一個判斷,key是否在當前instance的props中 問題3: 修改報錯,那就是只能讀,可以使用以前實現的 api shallowReadonly來包裹一下 既可
做完之後,可以發現咋們的測試用例是運行沒有毛病的
上面實現了props,那麼emit也是少不了的,那麼接下來就來實現下emit
根據上面的測試用例,可以分析出:
解決辦法: 問題1: emit 是setup的第二個參數, 那麼可以在setup函數調用的時候,傳入第二個參數 問題2: 關於emit的第一個參數, 可以做條件判斷,把xxx-xxx的形式轉成xxxXxx的形式,然後加入on,最後在props中取找,存在則調用,不存在則不調用 問題3:emit的第二個參數, 則使用剩餘參數即可
到此就圓滿成功啦!
❹ 面試中的網紅Vue源碼解析之虛擬DOM,你知多少呢深入解讀diff演算法
眾所周知,在前端的面試中,面試官非常愛考dom和diff演算法。比如,可能會出現在以下場景
滴滴滴,面試官發來一個面試邀請。接受邀請📞
我們都知道, key 的作用在前端的面試是一道很普遍的題目,但是呢,很多時候我們都只浮於知識的表面,而沒有去深挖其原理所在,這個時候我們的競爭力就在這被拉下了。所以呢,深入學習原理對於提升自身的核心競爭力是一個必不可少的過程。
在接下來的這篇文章中,我們將講解面試中很愛考的虛擬DOM以及其背後的diff演算法。 請認真閱讀本文~文末有學習資源免費共享!!!
虛擬DOM是用JavaScript對象描述DOM的層次結構。DOM中的一切屬性都在虛擬DOM中有對應的屬性。本質上是JS 和 DOM 之間的一個映射緩存。
要點:虛擬 DOM 是 JS 對象;虛擬 DOM 是對真實 DOM 的描述。
diff發生在虛擬DOM上。diff演算法是在新虛擬DOM和老虛擬DOM進行diff(精細化比對),實現最小量更新,最後反映到真正的DOM上。
我們前面知道diff演算法發生在虛擬DOM上,而虛擬DOM是如何實現的呢?實際上虛擬DOM是有一個個虛擬節點組成。
h函數用來產生虛擬節點(vnode)。虛擬節點有如下的屬性:
1)sel: 標簽類型,例如 p、div;
2)data: 標簽上的數據,例如 style、class、data-*;
3)children :子節點;
4) text: 文本內容;
5)elm:虛擬節點綁定的真實 DOM 節點;
通過h函數的嵌套,從而得到虛擬DOM樹。
我們編寫了一個低配版的h函數,必須傳入3個參數,重載較弱。
形態1:h('div', {}, '文字')
形態2:h('div', {}, [])
形態3:h('div', {}, h())
首先定義vnode節點,實際上就是把傳入的參數合成對象返回。
[圖片上傳失敗...(image-7a9966-1624019394657)]
然後編寫h函數,根據第三個參數的不同進行不同的響應。
當我們進行比較的過程中,我們採用的4種命中查找策略:
1)新前與舊前:命中則指針同時往後移動。
2)新後與舊後:命中則指針同時往前移動。
3)新後與舊前:命中則涉及節點移動,那麼新後指向的節點,移到 舊後之後 。
4)新前與舊後:命中則涉及節點移動,那麼新前指向的節點,移到 舊前之前 。
命中上述4種一種就不在命中判斷了,如果沒有命中,就需要循環來尋找,移動到舊前之前。直到while(新前<=新後&&舊前<=就後)不成立則完成。
如果是新節點先循環完畢,如果老節點中還有剩餘節點(舊前和舊後指針中間的節點),說明他們是要被刪除的節點。
如果是舊節點先循環完畢,說明新節點中有要插入的節點。
1.什麼是Virtual DOM 和Snabbdom
2.手寫底層源碼h函數
3.感受Vue核心演算法之diff演算法
4.snabbdom之核心h函數的工作原理
1、零基礎入門或者有一定基礎的同學、大中院校學生
2、在職從事相關工作1-2年以及打算轉行前端的朋友
3、對前端開發有興趣人群