理解虛擬DOM機制和key屬性的作用


什么是Vue的虛擬DMO?

虛擬DOM是區別於真實的DOM提出的。

在js事件直接操作DOM的時代(包括Jquery的時代),我們通過JS直接對真實的DOM樹進行增刪改查。
image
但是JS事件直接操作DOM會隨着項目規模的擴大、事件的增加導致事件的管理以及事件和DOM之間的關系的維護變得日益復雜。

image
為了解決這個問題,以Vue為代表的新型前端框架(包括react等)提出了引入數據中間層來避免直接操作DOM的思路:讓前端框架底層代替用戶去操作DOM,用戶不再關注DOM元素,而聚焦於數據(state)。使用Vue等框架,我們只需要修改數據,數據變化后Vue幫助我們來更新DOM。
image
但是DOM的變化是非常消耗計算機資源的,如何盡量的減少DOM的更新,是Vue需要考慮的。於是虛擬DOM的概念隨之被提出。所謂的虛擬DOM並不是一個真正的DOM樹,它是Vue底層在檢測到數據修改后,不立刻直接去修改真實的DOM,而是結合數據和模板,生成一個臨時的由js對象模擬的DOM樹。
image

Vue的實際DOM樹與虛擬DOM樹比較算法

獲得虛擬DOM之后,Vue底層會通過算法計算真實DOM樹與虛擬DOM樹的區別,並得到需要更新的節點,盡可能的復用現成的DOM節點,而不是去更新全部的DOM樹,從而達到減少計算資源消耗的目的。

在實際項目中,DOM樹可能極其復雜。為了提升真實DOM樹和虛擬DOM樹之間的比較效率,Vue提出了同層級比較的算法。即每次只比較處於同一個層級的DOM元素的變化情況。不同層級的不予比較。如下圖所示就是只比較相同顏色區域是否發生變化。
image
那么Vue的同層比較算法具體是什么判斷和處理邏輯呢?

它的基本邏輯是:當同一層的DOM節點中,如果能夠判斷節點的唯一性,那么盡可能的采用移動,插入等邏輯保持復用。如果不能保證唯一性,那么則采用更新,刪除等操作實現目的。

Vue算法實現舉例:
  1. 如下圖這種情況(同一層級,不同的元素——元素類型唯一)
    image
    Vue能夠根據元素類型判斷BCD為三個不同的唯一的元素,故而采取了性能高消耗少的移動邏輯。即將BCD的順序直接調整為CDB。全部復用了所用的DOM。

  2. 如下圖這種情況(不同層級,不同元素)
    image
    Vue雖然能夠根據元素類型判斷BCD為三個不同的唯一元素,但是Vue的算法是同層比較,當Vue掃描時發現C節點不存在了,於是直接對C節點進行了刪除,包括c節點下原有的EF。當遞歸到下一個層級時再創建新的c節點和ef節點。即BCD變成BD,Vue並不是把C移動到B下面,而是刪除原有的C,重新在B下面新建了C及其下級的EF。沒有復用C,E,F

  3. 如下圖這種情況(DOM的同層級,元素類型內容變化)
    image
    Vue檢測到同一層不再存在C而是存在G,於是算法刪除了C包括其下屬的EF,新建了G;當遞歸到下一層級時,再為G創建了EF。沒有復用EF。

  4. 如下圖這種情況(DOM同層級,相同的元素——無法判斷元素的唯一性)
    image
    這種情況下Vue無法通過元素類型判斷元素的唯一,也沒有key屬性幫助其判斷元素唯一,故而Vue認為元素不是唯一的。此時它會首先將B1更新成B2,再將B2更新成B1,並刪除原B2下的EF。當遞歸到下一層級時,為新的B2新建EF。此種情況Vue無法復用EF。

  5. 如下圖這種情況(DOM同類型節點的同層級順序變化,有Key屬性的情況下)。
    image
    當有key存在時,Vue底層能夠判斷節點的唯一性,故而Vue是采取的將B2節點包括下屬的EF節點移動到B1之前的邏輯。完全復用了B2,E,F

  6. 如下圖這種情況。(同層級,新增元素,無key)
    image
    因為vue無法確定元素的唯一性,故而vue認為用戶是想要更新節點。因此它會先將B2更新成B4,將B3更新成B2,最后新增一個B3.無法復用B2,B3

  7. 如下圖這種情況。(同層級,新增元素,有key)
    image
    Vue通過key確定唯一性,會將b4直接插入到b1和b2之間,達到復用B2,B3的目的。

App.vue的唯一性改造

通過上述7種情況的描述,我們發現如果沒有為Vue指定key屬性,那么Vue在操作DOM時的效率,不會達到最優。

回到我們的todoitem組件,我們之前在組件中綁定了Key屬性,但是我們的key屬性綁定的是title,現在看來很不嚴謹,因為title極有可能出現重復,而一旦出現重復,vue在操作DOM的時候就有可能會采用消耗較大的處理邏輯。

<template>
  <div id="app">
    <input type="text" v-model="message"/>
    <input type="text" :value="message" @input="handleChange"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.title" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>

我們可以考慮將此處的key綁定為v-for的循環下標

<template>
  <div id="app">
    <input type="text" v-model="message"/>
    <input type="text" :value="message" @input="handleChange"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="(item,index) in list" :key="index" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>

當然如果進一步考慮,假設list是一個一直在不斷的被增刪改查,排除的列表使用list下標也是不嚴謹的,最佳辦法則是為組件的數據增加id返回值。

  data(){
    return{
      message:"hello world",
      Checkedmsg:false,
      list: [
              {
                title: "新課程1",
                del: false,
                id:1
              },
              {
                title: "新課程2",
                del: true,
                id:2
              },
              {
                title: "新課程3",
                del: false,
                id:3
              }
            ]
    };
  },

模板中使用id綁定key

<template>
  <div id="app">
    <input type="text" v-model="message"/>
    <input type="text" :value="message" @input="handleChange"/>
    {{message}}
    <todolist>
      <todoitem v-on:delete="handleDelete" v-model="Checkedmsg" v-for="item in list" :key="item.id" data-wen="wen" :title="item.title" :del="item.del">
        <template v-slot:pretext="{val}">
          <label>前置文字{{val}}</label>
        </template>
      </todoitem>
    </todolist>
  </div>
</template>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM