日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
動手寫一個簡易的VirtualDOM,加強(qiáng)閱讀源碼的能力

你可能聽說過Virtual DOM(以及Shadow DOM)。甚至可能使用過它(JSX基本上是VDOM的語法糖)。如果你想了解更多,那么就看看今天這篇文章。

什么是虛擬DOM?

DOM操作很貴。做一次時,差異可能看起來很小(分配一個屬性給一個對象之間大約0.4毫秒的差異),但它會隨著時間的推移而增加。

 
 
 
 
  1. // 將屬性賦值給對象1000次
  2. let obj = {};
  3. console.time("obj");
  4. for (let i = 0; i < 1000; i++) {
  5.   obj[i] = i;
  6. }
  7. console.timeEnd("obj");
  8. // 操縱dom 1000次
  9. console.time("dom");
  10. for (let i = 0; i < 1000; i++) {
  11.   document.querySelector(".some-element").innerHTML += i;
  12. }
  13. console.timeEnd("dom");

當(dāng)我運行上面的代碼片段時,我發(fā)現(xiàn)第一個循環(huán)花費了約3ms,而第二個循環(huán)花費了約41ms。

我們舉一個更真實的例子。

 
 
 
 
  1. function generateList(list) {
  2.     let ul = document.createElement('ul');
  3.     document.getElementByClassName('.fruits').appendChild(ul);
  4.     list.forEach(function (item) {
  5.         let li = document.createElement('li');
  6.         ul.appendChild(li);
  7.         li.innerHTML += item;
  8.     });
  9.     return ul;
  10. }
  11. document.querySelector("ul.some-selector").innerHTML = generateList(["Banana", "Apple", "Orange"])

到目前為止,一切都好。現(xiàn)在,如果數(shù)組改變,我們需要重新渲染,我們這樣做:

 
 
 
 
  1. document.querySelector("ul.some-selector").innerHTML = generateList(["Banana", "Apple", "Mango"])

看看出了什么問題?

即使只需要改變一個元素,我們也會改變整個元素,因為我們很懶。

這就是為什么創(chuàng)建了虛擬DOM的原因。那什么是虛擬 Dom?

Virtual DOM是DOM作為對象的表示。假設(shè)我們有下面的 HTML:

  
 
 
 
  1.     

    Text here

  2.     

    Some other Bold content

 它可以寫作以下VDOM對象:

 
 
 
 
  1. let vdom = {
  2.     tag: "div",
  3.     props: { class: 'contents' },
  4.     children: [
  5.         {
  6.             tag: "p",
  7.             children: "Text here"
  8.         },
  9.         {
  10.             tag: "p",
  11.             children: ["Some other ", { tag: "b", children: "Bold" }, " content"]
  12.         }
  13.     ]
  14. }

請注意,實際開發(fā)中可能存在更多屬性,這是一個簡化的版本。

VDOM是一個對象,帶有:

  • 一個名為tag(有時也稱為type)的屬性,它表示標(biāo)簽的名稱
  • 一個名為props的屬性,包含所有 props
  • 如果內(nèi)容只是文本,則為字符串
  • 如果內(nèi)容包含元素,則vdom數(shù)組

我們這樣使用 VDOM:

  • 我們改變了vdom而不是dom
  • 函數(shù)檢查DOM和VDOM之間的所有差異,只更改變化的部分
  • 改變VDOM被標(biāo)記為最新的改變,這樣我們下次比較VDOM時就可以節(jié)省更多的時間。

有什么好處?

知道了什么是 VDOM,我們來改進(jìn)一下前面的 generateList函數(shù)。

 
 
 
 
  1. function generateList(list) {
  2.     // VDOM 生成過程,待下補(bǔ)上
  3. }
  4. patch(oldUL, generateList(["Banana", "Apple", "Orange"]));

不要介意patch函數(shù),它的作用是就將更改的部分附加到DOM中。以后再改變DOM時:

 
 
 
 
  1. patch(oldUL, generateList(["Banana", "Apple", "Mango"]));

patch函數(shù)發(fā)現(xiàn)只有第三個li發(fā)生了變化,,而不是所有三個元素都發(fā)生了變化,所以只會操作第三個 li 元素。

構(gòu)建 VDOM!

我們需要做4件事:

  • 創(chuàng)建一個虛擬節(jié)點(vnode)
  • 掛載 VDOM
  • 卸載 VDOM
  • Patch (比較兩個vnode,找出差異,然后掛載)

創(chuàng)建 vnode

 
 
 
 
  1. function createVNode(tag, props = {}, children = []) {
  2.     return { tag, props, children}
  3. }

在Vue(和許多其他地方)中,此函數(shù)稱為h,hyperscript 的縮寫。

掛載 VDOM

通過掛載,將vnode附加到任何容器,如#app或任何其他應(yīng)該掛載它的地方。

這個函數(shù)將遞歸遍歷所有節(jié)點的子節(jié)點,并將它們掛載到各自的容器中。

注意,下面的所有代碼都放在掛載函數(shù)中。

 
 
 
 
  1. function mount(vnode, container) { ... }

創(chuàng)建DOM元素

 
 
 
 
  1. const element = (vnode.element = document.createElement(vnode.tag))

你可能會想這個vnode.element是什么。它只是一個內(nèi)部設(shè)置的屬性,我們可以根據(jù)它知道哪個元素是vnode的父元素。

從props 對象設(shè)置所有屬性。我們可以對它們進(jìn)行循環(huán)

 
 
 
 
  1. Object.entries(vnode.props || {}).forEach([key, value] => {
  2.     element.setAttribute(key, value)
  3. })

掛載子元素,有兩種情況需要處理:

  • children 只是文本
  • children 是 vnode 數(shù)組
 
 
 
 
  1. if (typeof vnode.children === 'string') {
  2.     element.textContent = vnode.children
  3. } else {
  4.     vnode.children.forEach(child => {
  5.         mount(child, element) // 遞歸掛載子節(jié)點
  6.     })
  7. }

最后,我們必須將內(nèi)容添加到DOM中:

 
 
 
 
  1. container.appendChild(element)

最終的結(jié)果:

 
 
 
 
  1. function mount(vnode, container) { 
  2.     const element = (vnode.element = document.createElement(vnode.tag))
  3.     Object.entries(vnode.props || {}).forEach([key, value] => {
  4.         element.setAttribute(key, value)
  5.     })
  6.     if (typeof vnode.children === 'string') {
  7.         element.textContent = vnode.children
  8.     } else {
  9.         vnode.children.forEach(child => {
  10.             mount(child, element) // Recursively mount the children
  11.         })
  12.     }
  13.     container.appendChild(element)
  14. }

卸載 vnode

卸載就像從DOM中刪除一個元素一樣簡單:

 
 
 
 
  1. function unmount(vnode) {
  2.     vnode.element.parentNode.removeChild(vnode.element)
  3. }

patch vnode.

這是我們必須編寫的(相對而言)最復(fù)雜的函數(shù)。要做的事情就是找出兩個vnode之間的區(qū)別,只對更改部分進(jìn)行 patch。

 
 
 
 
  1. function patch(VNode1, VNode2) {
  2.     // 指定父級元素
  3.     const element = (VNode2.element = VNode1.element);
  4.     // 現(xiàn)在我們要檢查兩個vnode之間的區(qū)別
  5.     // 如果節(jié)點具有不同的標(biāo)記,則說明整個內(nèi)容已經(jīng)更改。
  6.     if (VNode1.tag !== VNode2.tag) {
  7.         // 只需卸載舊節(jié)點并掛載新節(jié)點
  8.         mount(VNode2, element.parentNode)
  9.         unmount(Vnode1)
  10.     } else {
  11.         // 節(jié)點具有相同的標(biāo)簽
  12.         // 所以我們要檢查兩個部分
  13.         // - Props
  14.         // - Children
  15.         // 這里不打算檢查 Props,因為它會增加代碼的復(fù)雜性,我們先來看怎么檢查 Children 就行啦
  16.         // 檢查 Children
  17.         // 如果新節(jié)點的 children 是字符串
  18.         if (typeof VNode2.children == "string") {
  19.             // 如果兩個孩子完全不同
  20.             if (VNode2.children !== VNode1.children) {
  21.                 element.textContent = VNode2.children;
  22.             }
  23.         } else {
  24.             // 如果新節(jié)點的 children 是一個數(shù)組
  25.             // - children 的長度是一樣的
  26.             // - 舊節(jié)點比新節(jié)點有更多的子節(jié)點
  27.             // - 新節(jié)點比舊節(jié)點有更多的子節(jié)點
  28.             // 檢查長度
  29.             const children1 = VNode1.children;
  30.             const children2 = VNode2.children;
  31.             const commonLen = Math.min(children1.length, children2.length)
  32.             // 遞歸地調(diào)用所有公共子節(jié)點的patch
  33.             for (let i = 0; i < commonLen; i++) {
  34.                 patch(children1[i], children2[i])
  35.             }
  36.             // 如果新節(jié)點的children 比舊節(jié)點的少
  37.             if (children1.length > children2.length) {
  38.                 children1.slice(children2.length).forEach(child => {
  39.                     unmount(child)
  40.                 })
  41.             }
  42.             //  如果新節(jié)點的children 比舊節(jié)點的多
  43.             if (children2.length > children1.length) {
  44.                 children2.slice(children1.length).forEach(child => {
  45.                     mount(child, element)
  46.                 })
  47.             }
  48.         }
  49.     }
  50. }

這是vdom實現(xiàn)的一個基本版本,方便我們快速掌握這個概念。當(dāng)然還有一些事情要做,包括檢查 props 和一些性能方面的改進(jìn)。

現(xiàn)在讓我們渲染一個vdom!

回到generateList例子。對于我們的vdom實現(xiàn),我們可以這樣做

 
 
 
 
  1. function generateList(list) {
  2.     let children = list.map(child => createVNode("li", null, child));
  3.     return createVNode("ul", { class: 'fruits-ul' }, children)
  4. }
  5. mount(generateList(["apple", "banana", "orange"]), document.querySelector("#app")/* any selector */)

線上示例:https://codepen.io/SiddharthShyniben/pen/MWpQrwM

~完,我是小智,SPA 走一波,下期見!

作者:Siddharth

譯者:前端小智 來源:dev原文:https://dev.to/siddharthshyniben/what-is-the-virtual-dom-let-s-build-it-5070


網(wǎng)站名稱:動手寫一個簡易的VirtualDOM,加強(qiáng)閱讀源碼的能力
文章轉(zhuǎn)載:http://www.5511xx.com/article/dhgeioc.html