Snabbdom.js(三)

總共寫了四篇文章(都是本身的一些拙見,僅供參考,請多多指教,我這邊也會持續修正加更新)html

  1. 介紹一下snabbdom基本用法
  2. 介紹一下snabbdom渲染原理
  3. 介紹一下snabddom的diff算法和對key值的認識
  4. 介紹一下對於兼容IE8的修改

這篇主要是說一下snabbdom的diff算法node

在上一篇中我總結過:
對比渲染的流程大致分爲
1.經過sameVnode來判斷兩個vnode是否值得進行比較
2.若是不值得,直接刪除舊的vnode,渲染新的vnode
3.若是值得,調用模塊鉤子函數,對其節點的屬性進行替換,例如style,event等;再判斷節點子節點是否爲文本節點,若是爲文本節點則進行更替,若是還存在其餘子節點則調用updateChildren,對子節點進行更新,更新流程將會回到第一步,重複;算法

這篇文章的重點就是說一下updateChildren這個函數segmentfault

sameVnode

function sameVnode(vnode1, vnode2) {
    return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}

這是一個比較兩個vnode是否類似,是否值得去進行比較的函數,那麼這裏爲何會提到它?由於這裏面有一個很重要的值---key
在平時的使用中幾乎用不到這個key值,不會去專門給它一個定義值,由於undefined===undefined,不會影響其比較;數組

key的做用

key值的出現主要是爲了應付一些場景
例如:dom

<ul>                   <ul>
    <li>1</li>            <li>2</li>
    <li>2</li>   -->      <li>3</li>
    <li>3</li>            <li>4</li>
</ul>                  </ul>

對於這種狀況,若是按照正常的作法,就是一個個vnode去進行比較,發現其文本節點不對,就會一個個進行替換例如:<li>1</li>--><li>2</li> .... 這樣就會進行三次dom操做函數

對於這種狀況是否能夠優化呢?
答案是能夠的,咱們能夠刪除<li>1</li>,而後添加一個<li>4</li>,這樣就只進行了兩次dom操做就完成了須要的效果優化

那這裏就涉及到一個標記值,標記着在新vnode中還有哪些舊的vnode存在,key值就是充當着這個角色。指針

[1(key:a),2(key:b),3(key:c)]code

[2(key:b),3(key:c),4(key:d)]

[a,b,c] -> [a(x),b,c,d(+)] === [1,2,3] --> [1(x),2,3,4(+)]

key值與vnode造成了一個映射,能夠看到,咱們經過對key值的排序、增刪間接完成了對vnode的操做,使用最少的dom操做來完成了

如何對key值進行排序,增刪

那這裏就會有一個問題,咱們如何完成上面的操做呢?這個過程咱們能夠理解爲一種優化對比渲染的過程,也就是diff算法的核心

建議你們先看一下這一篇文章,圖文並茂

我這邊舉一個複雜的例子,記錄每一步的操做:

下面是頁面真實的dom,分別保存在本身vnode的elm屬性上;舊-->新

<ul>                   <ul>
    <li>a</li>            <li>a</li>
    <li>b</li>            <li>d</li>
    <li>c</li>            <li>f</li>
    <li>f</li>            <li>h</li>
    <li>e</li>    -->     <li>k</li>
    <li>d</li>            <li>b</li>
    <li>g</li>            <li>g</li>
</ul>                  </ul>

假設每一個元素都有一個key值一一對應,且不重複,它們的key值分別爲

a:1            a:1
    b:2            d:6
    c:3            f:4
    f:4    -->     h:8
    e:5            k:9
    d:6            b:2
    g:7            g:7

將舊新vnode分別放入兩個數組

old:[vnode,....] 
new:[vnode,....]

其實咱們是比較其key值是否相等,而後再決定如何排序,增刪vnode的位置,patch vnode,最終達到改變dom的目的,爲了方便理解,我這裏把其key值拿出來放入一個數組,每個key在數組中的索引都對應着相應的vnode在其數組中的索引,在真實代碼中是直接比較vnode.key值。

oldKey:[1,2,3,4,5,6,7] 

oldStartIdx:0
oldStartVal:1

oldEndIdx:6
oldEndVal:7

newKey:[1,6,4,8,9,2,7]

newStartIdx:0
newStartVal:1

newEndIdx:6
newEndVal:7

用的是雙指針的方法,頭尾同時開始掃描;
循環兩個數組,循環條件爲(old_startIndex <= old_endIndex && new_startIndex <= new_endIndex)

(下面說的patch是直接對vnoe.elm進行修改,調用前面的patchVnode函數,也就是直接對頁面的dom進行修改,及時比較及時修改)
  • 比較oldStartVal和newStartVal是否相等,若是相等則oldStartIdx和newStartIdx分別加1,並對oldStartVal對應的vnode進行patch,進入下一次循環;這個例子中oldStartVal==newStartVal,因此oldStartIdx:1 newStartIdx:1;若不相等,繼續比較;

    比較事後:
       oldStartIdx:1              oldEndIdx:6
       oldStartVal:2              oldEndVal:7
    
    
       newStartIdx:1              newEndIdx:6     
       newStartVal:6              newEndVal:7
    
    
    比較範圍縮小後:
       
       oldKey:[2,3,4,5,6,7] 
       newKey:[6,4,8,9,2,7]
       
    dom:
       <li>a</li>            
       <li>b</li>            
       <li>c</li>            
       <li>f</li>           
       <li>e</li>       
       <li>d</li>          
       <li>g</li>
    
     oldVnodeArray:舊的vnode數組
       [a,b,c,f,e,d,g]
  • 比較oldEndVal和newEndVal是否相等,若是相等則oldEndIdx和newEndIdx分別減1,並對oldEndVal對應的舊vnode進行patch,進入下一次循環;這裏oldEndVal==newEndVal,因此oldEndIdx:5 newEndIdx:5;若不相等,繼續比較;

    比較事後:
       oldStartIdx:1              oldEndIdx:5
       oldStartVal:2              oldEndVal:6
    
    
       newStartIdx:1              newEndIdx:5     
       newStartVal:6              newEndVal:2
    
    比較範圍縮小後:
       
       oldKey:[2,3,4,5,6] 
       newKey:[6,4,8,9,2]
    
    dom:
       <li>a</li>            
       <li>b</li>            
       <li>c</li>            
       <li>f</li>           
       <li>e</li>       
       <li>d</li>          
       <li>g</li>
    
     oldVnodeArray:舊的vnode數組
       [a,b,c,f,e,d,g]
  • 比較oldStartVal和newEndVal是否相等,若是相等則oldStartIdx和newEndIdx分別加1和減1,oldStartVal對應的vnode移動到oldEndVal對應的vnode後面,並對移動的vnode進行patch,進入下一次循環;這裏oldStartVal==newEndVal,因此oldStartIdx:2 newEndIdx:4;若不相等,繼續比較;

    比較事後:
       oldStartIdx:2              oldEndIdx:5
       oldStartVal:3              oldEndVal:6
    
    
       newStartIdx:1              newEndIdx:4     
       newStartVal:6              newEndVal:9
    
    比較範圍縮小後:
       
       oldKey:[3,4,5,6] 
       newKey:[6,4,8,9]
    dom:
       <li>a</li>                   
       <li>c</li>            
       <li>f</li>           
       <li>e</li>       
       <li>d</li> 
       <li>b</li>            
       <li>g</li>
    
     oldVnodeArray:舊的vnode數組
       [a,b,c,f,e,d,g]
  • 比較oldEndVal和newStartVal是否相等,若是相等則oldEndIdx和newStartIdx分別減1和加1,oldEndVal對應的vnode移動到oldStart對應的vnode前面,並對移動的vnode進行patch,進入下一次循環;這裏oldEndVal==newStartVal,因此oldEndIdx:4 newStartIdx:2;若不相等,繼續比較;

    比較事後:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:2              newEndIdx:4     
       newStartVal:4              newEndVal:9
    
    比較範圍縮小後:
       
       oldKey:[3,4,5] 
       newKey:[4,8,9]
       
    dom:
       <li>a</li>
       <li>d</li>                      
       <li>c</li>            
       <li>f</li>           
       <li>e</li>        
       <li>b</li>          
       <li>g</li>
    
     oldVnodeArray:舊的vnode數組
       [a,b,c,f,e,d,g]
  • 若不知足上述判斷條件,查找newStartVal對應的vnode是否存在於舊vnode數組中。若存在,移動這個舊的vnode到oldStartVal對應的vnode前面,並對這個移動的vnode進行patch,在舊的vnode數組中將其原來的位置置爲undefined,而且newStartIdx加1;

    比較事後:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:3              newEndIdx:4     
       newStartVal:8              newEndVal:9
       
    
    比較範圍縮小後:
       
       oldKey:[3,4,5] 
       newKey:[8,9]
       
    dom:
       <li>a</li>
       <li>d</li>
       <li>f</li>                      
       <li>c</li>                     
       <li>e</li>        
       <li>b</li>          
       <li>g</li>
    
     oldVnodeArray:舊的vnode數組
       [a,b,c,undefined,e,d,g]

    若不存在,則將這個newStartVal對應的vnde添加到oldStartVal對應的vnode前面,而且newStartIdx加1;

    比較事後:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:4              newEndIdx:4     
       newStartVal:9              newEndVal:9
    
    比較範圍縮小後:
       
       oldKey:[3,4,5] 
       newKey:[9]
       
    dom:
       <li>a</li>
       <li>d</li>
       <li>f</li>
       <li>h</li>                      
       <li>c</li>                     
       <li>e</li>        
       <li>b</li>          
       <li>g</li>
    
    oldVnodeArray:舊的vnode數組
       [a,b,c,undefined,e,d,g]
    
    這裏循環了兩次
    比較事後:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:5                      newEndIdx:4     
       newStartVal:undefined              newEndVal:9
       
    比較範圍縮小後:
       oldKey:[3,4,5] 
       newKey:[]
       
    dom:
       <li>a</li>
       <li>d</li>
       <li>f</li>
       <li>h</li>
       <li>k</li>                      
       <li>c</li>                     
       <li>e</li>        
       <li>b</li>          
       <li>g</li>
    
    oldVnodeArray:舊的vnode數組
       [a,b,c,undefined,e,d,g]
  • 循環結束,判斷新舊vnode的key值哪一個遍歷完,若是舊的便利完,若舊vnode數組遍歷完,則將剩餘的新vnode數組中的vnode進行添加;若新vnode數組遍歷完,則刪除剩餘的舊vnode數組中的vnode
    在上面例子中,咱們須要刪除oldVnodeArray中的三個vnode,索引分別爲3,4,5,從而刪除了vnode對應的elm
    最後獲得最終的dom結構

    <ul>
                <li>a</li>
                <li>d</li>
                <li>f</li>
                <li>h</li>
                <li>k</li>
                <li>b</li>
                <li>g</li>
               </ul>

    上面的例子沒有將全部狀況所有概括進來,不過應該包含了大部分狀況了。還須要注意的就是:

  • 上面只是提到了key值,其實比較兩個vnode是否類似還有一個sel屬性,必需要兩個都相等才行
  • 正常狀況下key值用到的地方也是ul-li tr-td這種子元素重複的場景,由於這種狀況下才會涉及到子元素順序改變還能複用
  • 經過上面的分析,其實還能夠發現一個key值的特色,就是惟一性和一一對應性。惟一性好理解,畢竟key值就是用來每一個vnode本身的標示;一一對應表明着是你舊vnode和新vnode中若是沒有改變,則其key值應保持不變,之因此要提這個是由於不少地方看到了進行循環渲染的時候其key值都是用的數組的index進行賦值

    若是考慮這種狀況
    <li>a</li>                <li>c</li>
    <li>b</li>     -->        <li>a</li>  
    <li>c</li>                <li>b</li>
    
    通常這種dom結構都是放在數組裏面循環輸出的,若是它們的key值是按照index進行賦值的話,就須要這個地方須要進行三次dom操做,就是依次修改其節點的文本值;
    [a(1),b(2),c(3)]
    
    [c(1),a(2),b(3)]
    
    那若是我使得它們改變前面對應的元素的key值不改變的,一一對應的話,這裏只須要一次dom操做,就是把<li>c</li>移動到最前面
    [a(1),b(2),c(3)]
    
    [c(3),a(1),b(2)]
相關文章
相關標籤/搜索