認識虛擬DOM和diff算法

1、認識虛擬dom

一、在沒有vuereact以前,咱們都使用的是原生的javascriptjq,咱們都是經過操做dom,來達到視圖更新的效果。(操做dom->視圖更新)javascript

二、而在vue中,咱們只需更改data中的數據,視圖就會更新,因此在vue中,簡單說就是數據改變,讓視圖更新。vue

可咱們有沒有想過爲何數據改變,視圖就更新呢?其實vue更新視圖,也仍是操做了dom,只不過是在vuereact框架內部來完成的。(數據改變->操做dom->視圖更新)java

但這就引出了一個問題,難道vue每次數據更改,都會操做dom呢,顯然這不是最優解,因此就推出了虛擬dom這個東西。node

什麼是虛擬dom虛擬dom就是用js來模擬dom結構。react

舉個栗子:git

<div id="app">  
<h1>認識虛擬dom</h1> 
 <ul>   
 <li>1</li>   
 <li>2</li>   
 <li>3</li> 
 </ul>
</div>
複製代碼

 以上例子中的dom結構,若是用js來表達是什麼樣的呢?github

用js來表達dom,他會先聲明一個對象,而後有三個屬性:面試

tag : 元素;
props: 屬性或者事件;
children: 子節點或者內容;

{  tag: 'div',  
   props: {    id: 'app',    className: 'container'  },  
   children: [   
     { tag: 'h1',      children: '認識虛擬dom'    },    
     { tag: 'ul',      props: {},   children: [        
         { tag: 'li',      props: '1',   },        
         { tag: 'li',      props: '2',   },        
         { tag: 'li',      props: '3',   },  
        ]   
     }  
    ]
}
複製代碼

這樣一來,咱們就用js來表達了dom結構。算法

那咱們要怎麼樣用虛擬dom來計算咱們的變動,來操做真實的dom呢?數組

2、虛擬DOM的好處

扯了這麼多,其實就是爲了讓咱們知道虛擬dom,那到底虛擬dom有什麼好處呢?它好在什麼地方呢?彆着急,這就來。

在這以前,先介紹一個東西。Snabbdomvue裏面的虛擬dom就是藉助它來完成的

在官方github上,咱們能夠看到他下面的例子

主要的目的,就是經過以前那個js對象結構來表示頁面的一些節點,來插入到他的空的容器裏。

var container = document.getElementById('container')
複製代碼

他裏面是用vnode這個變量來表示的,那個vnode的結構,就是以前所說的js dom結構。而後表示出來的節點以後,會經過一個叫Patch的方法,將虛擬節點vnode塞到某一個容器裏。

咱們能夠照着它這個來寫一個小demo,認識snabbdomvnode的好處:

一、先引入snabbdom這個庫

const snabbdom = window.snabbdom;
複製代碼

在這以前,偷了一下懶,直接cnd引入了

<script src="https://lib.baomitu.com/snabbdom/0.7.4/h.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-class.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-eventlisteners.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-props.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-style.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom.js"></script>
複製代碼

二、將vnode(虛擬節點)塞到容器中

const patch = snabbdom.init({  classModule,  propsModule,  styleModule,  eventListenersModule,})
複製代碼

三、建立vnode,也就是虛擬dom,(引入h函數,經過h函數來建立)

const h = snabbdom.h
複製代碼

四、在頁面上聲明一個空的容器,而後在js選中這個容器

<div id="container"></div>//一個空的容器
const container = document.getElementById('container')
複製代碼

五、建立vnode

let vnode = h("ul#list", {}, [  h('li.item', {}, '第一個'),  h('li.item', {}, '第二個'),  h('li.item', {}, '第三個'),])
複製代碼

六、使用patch函數,將vnode塞到容器中,第一個參數:容器。第二個參數:虛擬節點

patch(container, vnode)
複製代碼

七、能夠看到已經將那幾個元素插入空的container裏了

若是我須要點擊一個按鈕,來改變第二個的內容,須要怎麼作呢?

八、先在頁面上添加一個button,而後js選中他,添加click監聽事件

<button id="btn">點擊</button>const btn = document.getElementById('btn')btn.addEventListener('click', ()=> {  const newVnode = h('ul#list', {}, [    h('li.item', {}, '第一個'),    h('li.item', {}, '第二個,我變了'),    h('li.item', {}, '第三個'),    h('li.item', {}, '第四個'),  ])  patch(vnode, newVnode)  vnode = newVnode})
複製代碼

值得注意的是,點擊以後,需傳入一個新的vnode,而後在建立完vnode以後,再執行patch函數,用新的vnode來更新老的vnode,同時將新的vnode賦值vnode,方便下次用最新結果比較。

點擊以前:

點擊以後:

能夠看到第二個和第四個已經變了,由於第一個和第三個是沒變了,因此他並無從新渲染那兩個節點。

結論:因此到這咱們就知道了,虛擬dom會去計算你有沒有變化,從而去決定要不要去操做你那個dom

3、認識diff算法

以前說過,虛擬dom就是計算節點是否變動修改,來判斷是否操做dom元素來更新視圖。

那麼這個計算,就是用的diff算法計算的。因此diff算法是虛擬dom的核心。

咱們用虛擬dom來表述真實的dom,這樣作的目的,就是爲了計算最小的變化,根據這個最小的變化,來更新真實的dom結構。

如上圖,有兩個虛擬dom,咱們既要用diff算法,來比較更新咱們的虛擬dom。那麼它會怎麼作呢?

  1. 遍歷舊的虛擬dom

  2. 遍歷新的虛擬dom

  3. 從新排序

好比上圖,有一個改變,和一個新增,那麼他會根據這個變化,來從新編排這個虛擬dom。

but……,沒錯,可惡的可是來了,若是咱們只是爲了改一小部分,但是節點數又很是多,有1000個節點,那麼他就會計算1000^3,也就是10億次,顯然這不是咱們的初衷也是不可接受的。

因此vuereact內部裏作diff算法的時候,內部是作過優化的,咱們能夠來see see他們是怎麼作的優化。

本來的diff算法比較,是先遍歷舊的vnode,而後遍歷新的vnode,而後對比,最後再從新編排。

vuereact的是,

  1. 只比較同一層級,不作跨級比較。

  2. 比較標籤名,若是標籤名不一樣,直接刪除,不會繼續深度比較。

  3. 標籤名相同,key相同,就認爲是相同節點,不繼續深度比較。(因此你們知道咱們寫v-for循環的時候,爲啥提示要加上key了吧)

因此經過以上的步驟,vue將diff算法給優化了一波

如今若是有1000個節點,就只需計算1000次了,簡直nice。以下圖:

4、snabbdom之生成vnode源碼簡述

咱們能夠看到snabbdom/src/package/h.ts

咱們是經過h函數來生產vnode的,能夠看到這個h方法,能接受好幾種狀況的傳參,

再看github上官方的栗子:

能夠看到,這裏傳了sel data children,最後經過

return vnode(sel, data, children, text, undefined)
複製代碼

來返回。咱們執行這個h函數後,而後他往vnode裏面傳遞參數,包括sel, data, children, text, undefined,咱們能夠進一步看看vnode函數的代碼:路徑:snabbdom/src/package/vnode.ts

能夠看到這個vnode函數,接受了一堆參數,那堆參數就是上面傳遞進來的參數。

最終,這個函數會返回一個對象,這個對象就是表示節點的對象。

咱們來看下這個節點對象,須要的組成部分:

sel : 選擇器,好比div

data: 好比style onClick

children: childrentext只能有一個,要麼是children數組包含子元素,要麼text字符串內容

elm: 對應的真實的dom元素,好比將new vnode替換掉old vnode,那個old vnode就是elm

key: v-for所需聲明的那個屬性

執行完後,會將這些參數用對象的形式返回回去,返回到h函數那裏,因此咱們執行了h函數以後,就會生成對應的vnode結構,而後咱們就把vnode結構,保存在變量中使用。

5、總結

咱們今天所學的虛擬dom和diff算法,主要圍繞snabbdom來學習虛擬dom,它主要的一些核心方法有這幾個:vnode h patch,若是你們掌握了這些,面試的時候若是有虛擬dom的問題,也應該內心有底了。還有patch函數中的diff算法,還有那個key,你們也應該知道v-for的時候爲何要有了。

最後若是你們有什麼好的建議或想法,也歡迎交流。

相關文章
相關標籤/搜索