這篇文章主要介紹了虛擬DOM是什么的相關(guān)知識,內(nèi)容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇虛擬DOM是什么文章都會有所收獲,下面我們一起來看看吧。
創(chuàng)新互聯(lián)公司成立與2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目網(wǎng)站設(shè)計、網(wǎng)站建設(shè)網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元蘇尼特右做網(wǎng)站,已為上家服務(wù),為蘇尼特右各地企業(yè)和個人服務(wù),聯(lián)系電話:18982081108
Snabbdom 是一個虛擬 DOM 實現(xiàn)庫,推薦的原因一是代碼比較少,核心代碼只有幾百行;二是 Vue 就是借鑒此項目的思路來實現(xiàn)虛擬 DOM 的;三是這個項目的設(shè)計/實現(xiàn)和擴展思路值得參考。
snabb /snab/,瑞典語,意思是快速的。
調(diào)整好舒服的坐姿,打起精神我們要開始啦~ 要學(xué)習(xí)虛擬 DOM,我們得先知道 DOM 的基礎(chǔ)知識和用 JS 直接操作 DOM 的痛點在哪里。
DOM(Document Object Model)是一種文檔對象模型,用一個對象樹的結(jié)構(gòu)來表示一個 HTML/XML 文檔,樹的每個分支的終點都是一個節(jié)點(node),每個節(jié)點都包含著對象。DOM API 的方法讓你可以用特定方式操作這個樹,用這些方法你可以改變文檔的結(jié)構(gòu)、樣式或者內(nèi)容。
DOM 樹中的所有節(jié)點首先都是一個 Node
,Node
是一個基類。Element
,Text
和 Comment
都繼承于它。
換句話說,Element
,Text
和 Comment
是三種特殊的 Node
,它們分別叫做 ELEMENT_NODE
,TEXT_NODE
和 COMMENT_NODE
,代表的是元素節(jié)點(HTML 標(biāo)簽)、文本節(jié)點和注釋節(jié)點。其中 Element
還有一個子類是 HTMLElement
,那 HTMLElement
和 Element
有什么區(qū)別呢?HTMLElement
代表 HTML 中的元素,如:、
等,而有些元素并不是 HTML 標(biāo)準(zhǔn)的,比如
。可以用下面的方法來判斷這個元素是不是
HTMLElement
:
document.getElementById('myIMG') instanceof HTMLElement;
瀏覽器創(chuàng)建 DOM 是很“昂貴”的。來一個經(jīng)典示例,我們可以通過 document.createElement('p')
創(chuàng)建一個簡單的 p 元素,將屬性都打印出來康康:
可以看到打印出來的屬性非常多,當(dāng)頻繁地去更新復(fù)雜的 DOM 樹時,會產(chǎn)生性能問題。虛擬 DOM 就是用一個原生的 JS 對象去描述一個 DOM 節(jié)點,所以創(chuàng)建一個 JS 對象比創(chuàng)建一個 DOM 對象的代價要小很多。
VNode 就是 Snabbdom 中描述虛擬 DOM 的一個對象結(jié)構(gòu),內(nèi)容如下:
type Key = string | number | symbol; interface VNode { // CSS 選擇器,比如:'p#container'。 sel: string | undefined; // 通過 modules 操作 CSS classes、attributes 等。 data: VNodeData | undefined; // 虛擬子節(jié)點數(shù)組,數(shù)組元素也可以是 string。 children: Array| undefined; // 指向創(chuàng)建的真實 DOM 對象。 elm: Node | undefined; /** * text 屬性有兩種情況: * 1. 沒有設(shè)置 sel 選擇器,說明這個節(jié)點本身是一個文本節(jié)點。 * 2. 設(shè)置了 sel,說明這個節(jié)點的內(nèi)容是一個文本節(jié)點。 */ text: string | undefined; // 用于給已存在的 DOM 提供標(biāo)識,在同級元素之間必須唯一,有效避免不必要地重建操作。 key: Key | undefined; } // vnode.data 上的一些設(shè)置,class 或者生命周期函數(shù)鉤子等等。 interface VNodeData { props?: Props; attrs?: Attrs; class?: Classes; style?: VNodeStyle; dataset?: Dataset; on?: On; attachData?: AttachData; hook?: Hooks; key?: Key; ns?: string; // for SVGs fn?: () => VNode; // for thunks args?: any[]; // for thunks is?: string; // for custom elements v1 [key: string]: any; // for any other 3rd party module }
例如這樣定義一個 vnode 的對象:
const vnode = h( 'p#container', { class: { active: true } }, [ h('span', { style: { fontWeight: 'bold' } }, 'This is bold'), ' and this is just normal text' ]);
我們通過 h(sel, b, c)
函數(shù)來創(chuàng)建 vnode 對象。h()
代碼實現(xiàn)中主要是判斷了 b 和 c 參數(shù)是否存在,并處理成 data 和 children,children 最終會是數(shù)組的形式。最后通過 vnode()
函數(shù)返回上面定義的 VNode
類型格式。
先來一張運行流程的簡單示例圖,先有個大概的流程概念:
diff 處理是用來計算新老節(jié)點之間差異的處理過程。
再來看一段 Snabbdom 運行的示例代碼:
import { init, classModule, propsModule, styleModule, eventListenersModule, h, } from 'snabbdom'; const patch = init([ // 通過傳入模塊初始化 patch 函數(shù) classModule, // 開啟 classes 功能 propsModule, // 支持傳入 props styleModule, // 支持內(nèi)聯(lián)樣式同時支持動畫 eventListenersModule, // 添加事件監(jiān)聽 ]); // const container = document.getElementById('container'); const vnode = h( 'p#container.two.classes', { on: { click: someFn } }, [ h('span', { style: { fontWeight: 'bold' } }, 'This is bold'), ' and this is just normal text', h('a', { props: { href: '/foo' } }, "I'll take you places!"), ] ); // 傳入一個空的元素節(jié)點。 patch(container, vnode); const newVnode = h( 'p#container.two.classes', { on: { click: anotherEventHandler } }, [ h( 'span', { style: { fontWeight: 'normal', fontStyle: 'italic' } }, 'This is now italic type' ), ' and this is still just normal text', h('a', { props: { href: ''/bar' } }, "I'll take you places!"), ] ); // 再次調(diào)用 patch(),將舊節(jié)點更新為新節(jié)點。 patch(vnode, newVnode);
從流程示意圖和示例代碼可以看出,Snabbdom 的運行流程描述如下:
首先調(diào)用 init()
進行初始化,初始化時需要配置需要使用的模塊。比如 classModule
模塊用來使用對象的形式來配置元素的 class 屬性;eventListenersModule
模塊用來配置事件監(jiān)聽器等等。init()
調(diào)用后會返回 patch()
函數(shù)。
通過 h()
函數(shù)創(chuàng)建初始化 vnode 對象,調(diào)用 patch()
函數(shù)去更新,最后通過 createElm()
創(chuàng)建真正的 DOM 對象。
當(dāng)需要更新時,創(chuàng)建一個新的 vnode 對象,調(diào)用 patch()
函數(shù)去更新,經(jīng)過 patchVnode()
和 updateChildren()
完成本節(jié)點和子節(jié)點的差異更新。
Snabbdom 是通過模塊這種設(shè)計來擴展相關(guān)屬性的更新而不是全部寫到核心代碼中。那這是如何設(shè)計與實現(xiàn)的?接下來就先來康康這個設(shè)計的核心內(nèi)容,Hooks——生命周期函數(shù)。
Snabbdom 提供了一系列豐富的生命周期函數(shù)也就是鉤子函數(shù),這些生命周期函數(shù)適用在模塊中或者可以直接定義在 vnode 上。比如我們可以在 vnode 上這樣定義鉤子的執(zhí)行:
h('p.row', { key: 'myRow', hook: { insert: (vnode) => { console.log(vnode.elm.offsetHeight); }, }, });
全部的生命周期函數(shù)聲明如下:
名稱 | 觸發(fā)節(jié)點 | 回調(diào)參數(shù) |
---|---|---|
pre | patch 開始執(zhí)行 | none |
init | vnode 被添加 | vnode |
create | 一個基于 vnode 的 DOM 元素被創(chuàng)建 | emptyVnode, vnode |
insert | 元素被插入到 DOM | vnode |
prepatch | 元素即將 patch | oldVnode, vnode |
update | 元素已更新 | oldVnode, vnode |
postpatch | 元素已被 patch | oldVnode, vnode |
destroy | 元素被直接或間接得移除 | vnode |
remove | 元素已從 DOM 中移除 | vnode, removeCallback |
post | 已完成 patch 過程 | none |
其中適用于模塊的是:pre
, create
,update
, destroy
, remove
, post
。適用于 vnode 聲明的是:init
, create
, insert
, prepatch
, update
,postpatch
, destroy
, remove
。
我們來康康是如何實現(xiàn)的,比如我們以 classModule
模塊為例,康康它的聲明:
import { VNode, VNodeData } from "../vnode"; import { Module } from "./module"; export type Classes = Record; function updateClass(oldVnode: VNode, vnode: VNode): void { // 這里是更新 class 屬性的細節(jié),先不管。 // ... } export const classModule: Module = { create: updateClass, update: updateClass };
可以看到最后導(dǎo)出的模塊定義是一個對象,對象的 key 就是鉤子函數(shù)的名稱,模塊對象 Module
的定義如下:
import { PreHook, CreateHook, UpdateHook, DestroyHook, RemoveHook, PostHook, } from "../hooks"; export type Module = Partial<{ pre: PreHook; create: CreateHook; update: UpdateHook; destroy: DestroyHook; remove: RemoveHook; post: PostHook; }>;
TS 中 Partial
表示對象中每個 key 的屬性都是可以為空的,也就是說模塊定義中你關(guān)心哪個鉤子,就定義哪個鉤子就好了。鉤子的定義有了,在流程中是怎么執(zhí)行的呢?接著我們來看 init()
函數(shù):
// 模塊中可能定義的鉤子有哪些。 const hooks: Array= [ "create", "update", "remove", "destroy", "pre", "post", ]; export function init( modules: Array >, domApi?: DOMAPI, options?: Options ) { // 模塊中定義的鉤子函數(shù)最后會存在這里。 const cbs: ModuleHooks = { create: [], update: [], remove: [], destroy: [], pre: [], post: [], }; // ... // 遍歷模塊中定義的鉤子,并存起來。 for (const hook of hooks) { for (const module of modules) { const currentHook = module[hook]; if (currentHook !== undefined) { (cbs[hook] as any[]).push(currentHook); } } } // ... }
可以看到 init()
在執(zhí)行時先遍歷各個模塊,然后把鉤子函數(shù)存到了 cbs
這個對象中。執(zhí)行的時候可以康康 patch()
函數(shù)里面:
export function init( modules: Array>, domApi?: DOMAPI, options?: Options ) { // ... return function patch( oldVnode: VNode | Element | DocumentFragment, vnode: VNode ): VNode { // ... // patch 開始了,執(zhí)行 pre 鉤子。 for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); // ... } }
這里以 pre
這個鉤子舉例,pre
鉤子的執(zhí)行時機是在 patch 開始執(zhí)行時??梢钥吹?patch()
函數(shù)在執(zhí)行的開始處去循環(huán)調(diào)用了 cbs
中存儲的 pre
相關(guān)鉤子。其他生命周期函數(shù)的調(diào)用也跟這個類似,大家可以在源碼中其他地方看到對應(yīng)生命周期函數(shù)調(diào)用的地方。
這里的設(shè)計思路是觀察者模式。Snabbdom 把非核心功能分布在模塊中來實現(xiàn),結(jié)合生命周期的定義,模塊可以定義它自己感興趣的鉤子,然后 init()
執(zhí)行時處理成 cbs
對象就是注冊這些鉤子;當(dāng)執(zhí)行時間到來時,調(diào)用這些鉤子來通知模塊處理。這樣就把核心代碼和模塊代碼分離了出來,從這里我們可以看出觀察者模式是一種代碼解耦的常用模式。
接下來我們來康康核心函數(shù) patch()
,這個函數(shù)是在 init()
調(diào)用后返回的,作用是執(zhí)行 VNode 的掛載和更新,簽名如下:
function patch(oldVnode: VNode | Element | DocumentFragment, vnode: VNode): VNode { // 為簡單起見先不關(guān)注 DocumentFragment。 // ... }
oldVnode
參數(shù)是舊的 VNode 或 DOM 元素或文檔片段,vnode
參數(shù)是更新后的對象。這里我直接貼出整理的流程描述:
調(diào)用模塊上注冊的 pre
鉤子。
如果 oldVnode
是 Element
,則將其轉(zhuǎn)換為空的 vnode
對象,屬性里面記錄了 elm
。
這里判斷是不是 Element
是判斷 (oldVnode as any).nodeType === 1
是完成的,nodeType === 1
表明是一個 ELEMENT_NODE,定義在 這里。
然后判斷 oldVnode
和 vnode
是不是相同的,這里會調(diào)用 sameVnode()
來判斷:
function sameVnode(vnode1: VNode, vnode2: VNode): boolean { // 同樣的 key。 const isSameKey = vnode1.key === vnode2.key; // Web component,自定義元素標(biāo)簽名,看這里: // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement const isSameIs = vnode1.data?.is === vnode2.data?.is; // 同樣的選擇器。 const isSameSel = vnode1.sel === vnode2.sel; // 三者都相同即是相同的。 return isSameSel && isSameKey && isSameIs; }
如果相同,則調(diào)用 patchVnode()
做 diff 更新。
如果不同,則調(diào)用 createElm()
創(chuàng)建新的 DOM 節(jié)點;創(chuàng)建完畢后插入 DOM 節(jié)點并刪除舊的 DOM 節(jié)點。
調(diào)用上述操作中涉及的 vnode 對象中注冊的 insert
鉤子隊列, patchVnode()
createElm()
都可能會有新節(jié)點插入 。至于為什么這樣做,在 createElm()
中會說到。
最后調(diào)用模塊上注冊的 post
鉤子。
流程基本就是相同的 vnode 就做 diff,不同的就創(chuàng)建新的刪除舊的。接下來先看下 createElm()
是如何創(chuàng)建 DOM 節(jié)點的。
createElm()
是根據(jù) vnode 的配置來創(chuàng)建 DOM 節(jié)點。流程如下:
調(diào)用 vnode 對象上可能存在的 init
鉤子。
然后分一下幾種情況來處理:
解析選擇器,得到 id
、tag
和 class
。
調(diào)用 document.createElement()
或 document.createElementNS
創(chuàng)建 DOM 節(jié)點,并記錄到 vnode.elm
中,并根據(jù)上一步的結(jié)果來設(shè)置 id
、tag
和 class
。
調(diào)用模塊上的 create
鉤子。
處理 children
子節(jié)點數(shù)組:
調(diào)用 vnode 上的 create
鉤子。并將 vnode 上的 insert
鉤子加入到 insert
鉤子隊列。
如果 children
是數(shù)組,則遞歸調(diào)用 createElm()
創(chuàng)建子節(jié)點后,調(diào)用 appendChild
掛載到 vnode.elm
下。
如果 children
不是數(shù)組但 vnode.text
存在,說明這個元素的內(nèi)容是個文本,這個時候調(diào)用 createTextNode
創(chuàng)建文本節(jié)點并掛載到 vnode.elm
下。
如果 vnode.sel === '!'
,這是 Snabbdom 用來刪除原節(jié)點的方法,這樣會新插入一個注釋節(jié)點。因為在 createElm()
后會刪除老節(jié)點,所以這樣設(shè)置就可以達到卸載的目的。
如果 vnode.sel
選擇器定義是存在的:
剩下的情況就是 vnode.sel
不存在,說明節(jié)點本身是文本,那就調(diào)用 createTextNode
創(chuàng)建文本節(jié)點并記錄到 vnode.elm
。
最后返回 vnode.elm
。
整個過程可以看出 createElm()
是根據(jù) sel
選擇器的不同設(shè)置來選擇如何創(chuàng)建 DOM 節(jié)點。這里有個細節(jié)是補一下: patch()
中提到的 insert
鉤子隊列。需要這個 insert
鉤子隊列的原因是需要等到 DOM 真正被插入后才執(zhí)行,而且也要等到所有子孫節(jié)點都插入完成,這樣我們可以在 insert
中去計算元素的大小位置信息才是準(zhǔn)確的。結(jié)合上面創(chuàng)建子節(jié)點的過程,createElm()
創(chuàng)建子節(jié)點是遞歸調(diào)用,所以隊列會先記錄子節(jié)點,再記錄自身。這樣在 patch()
的結(jié)尾執(zhí)行這個隊列時就可以保證這個順序。
接下來我們來看 Snabbdom 如何用 patchVnode()
來做 diff 的,這是虛擬 DOM 的核心。patchVnode()
的處理流程如下:
首先執(zhí)行 vnode 上 prepatch
鉤子。
如果 oldVnode 和 vnode 是同一個對象引用,則不處理直接返回。
調(diào)用模塊和 vnode 上的 update
鉤子。
如果沒有定義 vnode.text
,則處理 children
的幾種情況:
如果 oldVnode.children
和 vnode.children
均存在并且不相同。則調(diào)用 updateChildren
去更新。
vnode.children
存在而 oldVnode.children
不存在。如果 oldVnode.text
存在則先清空,然后調(diào)用 addVnodes
去添加新的 vnode.children
。
vnode.children
不存在而 oldVnode.children
存在。調(diào)用 removeVnodes
移除 oldVnode.children
。
如果 oldVnode.children
和 vnode.children
均不存在。如果 oldVnode.text
存在則清空。
如果有定義 vnode.text
并且與 oldVnode.text
不同。如果 oldVnode.children
存在則調(diào)用 removeVnodes
清除。然后通過 textContent
來設(shè)置文本內(nèi)容。
最后執(zhí)行 vnode 上的 postpatch
鉤子。
從過程可以看出,diff 中對于自身節(jié)點的相關(guān)屬性的改變比如 class
、style
之類的是依靠模塊去更新的,這里不過多展開了大家有需要可以去看下模塊相關(guān)的代碼。diff 的主要核心處理是集中在 children
上,接下來康康 diff 處理 children
的幾個相關(guān)函數(shù)。
這個很簡單,先調(diào)用 createElm()
創(chuàng)建,然后插入到對應(yīng)的 parent 中。
移除的時候會先調(diào)用 destory
和 remove
鉤子,這里重點講講這兩個鉤子的調(diào)用邏輯和區(qū)別。
destory
,首先調(diào)用這個鉤子。邏輯是先調(diào)用 vnode 對象上的這個鉤子,再調(diào)用模塊上的。然后對 vnode.children
也按照這個順序遞歸調(diào)用這個鉤子。
remove
,這個 hook 只有在當(dāng)前元素從它的父級中刪除才會觸發(fā),被移除的元素中的子元素則不會觸發(fā),并且模塊和 vnode 對象上的這個鉤子都會調(diào)用,順序是先調(diào)用模塊上的再調(diào)用 vnode 上的。而且比較特殊的是等待所有的 remove
都會調(diào)用后,元素才會真正被移除,這樣做可以實現(xiàn)一些延遲刪除的需求。
以上可以看出這兩個鉤子調(diào)用邏輯不同的地方,特別是 remove
只在直接脫離父級的元素上才會被調(diào)用。
updateChildren()
是用來處理子節(jié)點 diff 的,也是 Snabbdom 中比較復(fù)雜的一個函數(shù)??偟乃枷胧菍? oldCh
和 newCh
各設(shè)置頭、尾一共四個指針,這四個指針分別是 oldStartIdx
、oldEndIdx
、newStartIdx
和 newEndIdx
。然后在 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
循環(huán)中對兩個數(shù)組進行對比,找到相同的部分進行復(fù)用更新,并且每次比較處理最多移動一對指針。詳細的遍歷過程按以下順序處理:
如果這四個指針有任何一個指向的 vnode == null,則這個指針往中間移動,比如:start++ 或 end--,null 的產(chǎn)生在后面情況有說明。
如果新舊開始節(jié)點相同,也就是 sameVnode(oldStartVnode, newStartVnode)
返回 true,則用 patchVnode()
執(zhí)行 diff,并且兩個開始節(jié)點都向中間前進一步。
如果新舊結(jié)束節(jié)點相同,也采用 patchVnode()
處理,兩個結(jié)束節(jié)點向中間后退一步。
如果舊開始節(jié)點與新結(jié)束節(jié)點相同,先用 patchVnode()
處理更新。然后需要移動 oldStart 對應(yīng)的 DOM 節(jié)點,移動的策略是移動到 oldEndVnode
對應(yīng) DOM 節(jié)點的下一個兄弟節(jié)點之前。為什么是這樣移動呢?首先,oldStart 與 newEnd 相同,說明在當(dāng)前循環(huán)處理中,老數(shù)組的開始節(jié)點是往右移動了;因為每次的處理都是首尾指針往中間移動,我們是把老數(shù)組更新成新的,這個時候 oldEnd 可能還沒處理,但這個時候 oldStart 已確定在新數(shù)組的當(dāng)前處理中是最后一個了,所以移動到 oldEnd 的下一個兄弟節(jié)點之前是合理的。移動完畢后,oldStart++,newEnd--,分別向各自的數(shù)組中間移動一步。
如果舊結(jié)束節(jié)點與新開始節(jié)點相同,也是先用 patchVnode()
處理更新,然后把 oldEnd 對應(yīng)的 DOM 節(jié)點移動 oldStartVnode
對應(yīng)的 DOM 節(jié)點之前,移動理由同上一步一樣。移動完畢后,oldEnd--,newStart++。
如果以上情況都不是,則通過 newStartVnode 的 key 去找在 oldChildren
的下標(biāo) idx,根據(jù)下標(biāo)是否存在有兩種不同的處理邏輯:
如果兩個 vnode 的 sel 不同,也還是當(dāng)做新創(chuàng)建的,通過 createElm()
創(chuàng)建新的 DOM,并插入到 oldStartVnode
對應(yīng)的 DOM 之前。
如果 sel 是相同的,則通過 patchVnode()
處理更新,并把 oldChildren
對應(yīng)下標(biāo)的 vnode 設(shè)置為 undefined,這也是前面雙指針遍歷中為什么會出現(xiàn) == null 的原因。然后把更新完畢后的節(jié)點插入到 oldStartVnode
對應(yīng)的 DOM 之前。
如果下標(biāo)不存在,說明 newStartVnode 是新創(chuàng)建的。通過 createElm()
創(chuàng)建新的 DOM,并插入到 oldStartVnode
對應(yīng)的 DOM 之前。
如果下標(biāo)存在,也要分兩種情況處理:
以上操作完后,newStart++。
遍歷結(jié)束后,還有兩種情況要處理。一種是 oldCh
已經(jīng)全部處理完成,而 newCh
中還有新的節(jié)點,需要對 newCh
剩下的每個都創(chuàng)建新的 DOM;另一種是 newCh
全部處理完成,而 oldCh
中還有舊的節(jié)點,需要將多余的節(jié)點移除。這兩種情況的處理在 如下:
function updateChildren( parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue ) { // 雙指針遍歷過程。 // ... // newCh 中還有新的節(jié)點需要創(chuàng)建。 if (newStartIdx <= newEndIdx) { // 需要插入到最后一個處理好的 newEndIdx 之前。 before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; addVnodes( parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue ); } // oldCh 中還有舊的節(jié)點要移除。 if (oldStartIdx <= oldEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
我們用一個實際例子來看一下 updateChildren()
的處理過程:
初始狀態(tài)如下,舊子節(jié)點數(shù)組為 [A, B, C],新節(jié)點數(shù)組為 [B, A, C, D]:
第一輪比較,開始和結(jié)束節(jié)點都不一樣,于是看 newStartVnode 在舊節(jié)點中是否存在,找到了在 oldCh[1] 這個位置,那么先執(zhí)行 patchVnode()
進行更新,然后把 oldCh[1] = undefined,并把 DOM 插入到 oldStartVnode
之前,newStartIdx
向后移動一步,處理完后狀態(tài)如下:
第二輪比較,oldStartVnode
和 newStartVnode
相同,執(zhí)行 patchVnode()
更新,oldStartIdx
和 newStartIdx
向中間移動,處理完后狀態(tài)如下:
第三輪比較,oldStartVnode == null
,oldStartIdx
向中間移動,狀態(tài)更新如下:
第四輪比較,oldStartVnode
和 newStartVnode
相同,執(zhí)行 patchVnode()
更新,oldStartIdx
和 newStartIdx
向中間移動,處理完后狀態(tài)如下:
此時 oldStartIdx
大于 oldEndIdx
,循環(huán)結(jié)束。此時 newCh
中還有沒處理完的新節(jié)點,需要調(diào)用 addVnodes()
插入,最終狀態(tài)如下:
關(guān)于“虛擬DOM是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“虛擬DOM是什么”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。