真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

虛擬DOM是什么

這篇文章主要介紹了虛擬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

虛擬DOM是什么

Snabbdom 是一個虛擬 DOM 實現(xiàn)庫,推薦的原因一是代碼比較少,核心代碼只有幾百行;二是 Vue 就是借鑒此項目的思路來實現(xiàn)虛擬 DOM 的;三是這個項目的設(shè)計/實現(xiàn)和擴展思路值得參考。

snabb /snab/,瑞典語,意思是快速的。

調(diào)整好舒服的坐姿,打起精神我們要開始啦~ 要學(xué)習(xí)虛擬 DOM,我們得先知道 DOM 的基礎(chǔ)知識和用 JS 直接操作 DOM 的痛點在哪里。

DOM 的作用和類型結(jié)構(gòu)

DOM(Document Object Model)是一種文檔對象模型,用一個對象樹的結(jié)構(gòu)來表示一個 HTML/XML 文檔,樹的每個分支的終點都是一個節(jié)點(node),每個節(jié)點都包含著對象。DOM API 的方法讓你可以用特定方式操作這個樹,用這些方法你可以改變文檔的結(jié)構(gòu)、樣式或者內(nèi)容。

DOM 樹中的所有節(jié)點首先都是一個 Node,Node 是一個基類。Element,TextComment 都繼承于它。
換句話說,Element,TextComment 是三種特殊的 Node,它們分別叫做 ELEMENT_NODE,
TEXT_NODECOMMENT_NODE,代表的是元素節(jié)點(HTML 標(biāo)簽)、文本節(jié)點和注釋節(jié)點。其中 Element 還有一個子類是 HTMLElement,那 HTMLElementElement 有什么區(qū)別呢?HTMLElement 代表 HTML 中的元素,如: 等,而有些元素并不是 HTML 標(biāo)準(zhǔn)的,比如 。可以用下面的方法來判斷這個元素是不是 HTMLElement

document.getElementById('myIMG') instanceof HTMLElement;

為什么需要虛擬 DOM?

瀏覽器創(chuàng)建 DOM 是很“昂貴”的。來一個經(jīng)典示例,我們可以通過 document.createElement('p') 創(chuàng)建一個簡單的 p 元素,將屬性都打印出來康康:

虛擬DOM是什么

可以看到打印出來的屬性非常多,當(dāng)頻繁地去更新復(fù)雜的 DOM 樹時,會產(chǎn)生性能問題。虛擬 DOM 就是用一個原生的 JS 對象去描述一個 DOM 節(jié)點,所以創(chuàng)建一個 JS 對象比創(chuàng)建一個 DOM 對象的代價要小很多。

VNode

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 類型格式。

Snabbdom 的運行流程

先來一張運行流程的簡單示例圖,先有個大概的流程概念:

虛擬DOM是什么

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ù)。

Hooks

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ù)
prepatch 開始執(zhí)行none
initvnode 被添加vnode
create一個基于 vnode 的 DOM 元素被創(chuàng)建emptyVnode, vnode
insert元素被插入到 DOMvnode
prepatch元素即將 patcholdVnode, vnode
update元素已更新oldVnode, vnode
postpatch元素已被 patcholdVnode, 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)用這些鉤子來通知模塊處理。這樣就把核心代碼和模塊代碼分離了出來,從這里我們可以看出觀察者模式是一種代碼解耦的常用模式。

patch()

接下來我們來康康核心函數(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 鉤子。

  • 如果 oldVnodeElement,則將其轉(zhuǎn)換為空的 vnode 對象,屬性里面記錄了 elm。

    這里判斷是不是 Element 是判斷 (oldVnode as any).nodeType === 1 是完成的,nodeType === 1 表明是一個 ELEMENT_NODE,定義在 這里。

  • 然后判斷 oldVnodevnode 是不是相同的,這里會調(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()

createElm() 是根據(jù) vnode 的配置來創(chuàng)建 DOM 節(jié)點。流程如下:

  • 調(diào)用 vnode 對象上可能存在的 init 鉤子。

  • 然后分一下幾種情況來處理:

    • 解析選擇器,得到 id、tagclass

    • 調(diào)用 document.createElement()document.createElementNS 創(chuàng)建 DOM 節(jié)點,并記錄到 vnode.elm 中,并根據(jù)上一步的結(jié)果來設(shè)置 id、tagclass。

    • 調(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í)行這個隊列時就可以保證這個順序。

patchVnode()

接下來我們來看 Snabbdom 如何用 patchVnode() 來做 diff 的,這是虛擬 DOM 的核心。patchVnode() 的處理流程如下:

  • 首先執(zhí)行 vnode 上 prepatch 鉤子。

  • 如果 oldVnode 和 vnode 是同一個對象引用,則不處理直接返回。

  • 調(diào)用模塊和 vnode 上的 update 鉤子。

  • 如果沒有定義 vnode.text,則處理 children 的幾種情況:

    • 如果 oldVnode.childrenvnode.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.childrenvnode.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ù)。

addVnodes()

這個很簡單,先調(diào)用 createElm() 創(chuàng)建,然后插入到對應(yīng)的 parent 中。

removeVnodes()

移除的時候會先調(diào)用 destoryremove 鉤子,這里重點講講這兩個鉤子的調(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()

updateChildren() 是用來處理子節(jié)點 diff 的,也是  Snabbdom  中比較復(fù)雜的一個函數(shù)??偟乃枷胧菍? oldChnewCh 各設(shè)置頭、尾一共四個指針,這四個指針分別是 oldStartIdxoldEndIdx、newStartIdxnewEndIdx。然后在 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]:

虛擬DOM是什么

  • 第一輪比較,開始和結(jié)束節(jié)點都不一樣,于是看 newStartVnode 在舊節(jié)點中是否存在,找到了在 oldCh[1] 這個位置,那么先執(zhí)行 patchVnode() 進行更新,然后把 oldCh[1] = undefined,并把 DOM 插入到 oldStartVnode 之前,newStartIdx 向后移動一步,處理完后狀態(tài)如下:

虛擬DOM是什么

  • 第二輪比較,oldStartVnodenewStartVnode 相同,執(zhí)行 patchVnode() 更新,oldStartIdxnewStartIdx 向中間移動,處理完后狀態(tài)如下:

虛擬DOM是什么

  • 第三輪比較,oldStartVnode == nulloldStartIdx 向中間移動,狀態(tài)更新如下:

虛擬DOM是什么

  • 第四輪比較,oldStartVnodenewStartVnode 相同,執(zhí)行 patchVnode() 更新,oldStartIdxnewStartIdx 向中間移動,處理完后狀態(tài)如下:

虛擬DOM是什么

  • 此時 oldStartIdx 大于 oldEndIdx,循環(huán)結(jié)束。此時 newCh 中還有沒處理完的新節(jié)點,需要調(diào)用 addVnodes() 插入,最終狀態(tài)如下:

虛擬DOM是什么

關(guān)于“虛擬DOM是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“虛擬DOM是什么”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


分享文章:虛擬DOM是什么
標(biāo)題網(wǎng)址:http://weahome.cn/article/jehoij.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部