自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

動手寫一個簡易的 Virtual DOM,加強(qiáng)閱讀源碼的能力

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

[[409084]]

你可能聽說過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. console.timeEnd("obj"); 
  7.  
  8. // 操縱dom 1000次 
  9. console.time("dom"); 
  10. for (let i = 0; i < 1000; i++) { 
  11.   document.querySelector(".some-element").innerHTML += i; 
  12. console.timeEnd("dom"); 

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

我們舉一個更真實(shí)的例子。

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

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

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

看看出了什么問題?

即使只需要改變一個元素,我們也會改變整個元素,因?yàn)槲覀兒軕小?/p>

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

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

  1. <div class="contents"
  2.     <p>Text here</p> 
  3.     <p>Some other <b>Bold</b> content</p> 
  4. </div> 

 它可以寫作以下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.     ] 

請注意,實(shí)際開發(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é)點(diǎn)(vnode)
  • 掛載 VDOM
  • 卸載 VDOM
  • Patch (比較兩個vnode,找出差異,然后掛載)

創(chuàng)建 vnode

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

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

掛載 VDOM

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

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

注意,下面的所有代碼都放在掛載函數(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é)點(diǎn) 
  6.     }) 

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

  1. container.appendChild(element) 

最終的結(jié)果:

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

卸載 vnode

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

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

patch vnode.

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

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

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

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

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

  1. function generateList(list) { 
  2.     let children = list.map(child => createVNode("li"null, child)); 
  3.  
  4.     return createVNode("ul", { class: 'fruits-ul' }, children) 
  5.  
  6. 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

 

責(zé)任編輯:姜華 來源: 大遷世界
相關(guān)推薦

2017-03-02 13:31:02

監(jiān)控系統(tǒng)

2022-01-10 11:04:41

單鏈表面試編程

2023-12-16 13:21:00

Python元類ORM

2015-06-02 10:24:43

iOS網(wǎng)絡(luò)請求降低耦合

2015-06-02 09:51:40

iOS網(wǎng)絡(luò)請求封裝接口

2021-05-26 05:22:09

Virtual DOMSnabbdom虛擬DOM

2022-05-06 19:42:53

DOM

2015-06-02 09:41:00

iOS網(wǎng)絡(luò)請求NSURLSessio

2021-01-28 07:21:13

算法虛擬DOM前端

2014-02-14 09:37:01

JavascriptDOM

2020-10-12 08:56:47

Virtual dom

2021-02-20 09:45:02

RPC框架Java

2022-10-31 08:27:53

Database數(shù)據(jù)數(shù)據(jù)庫

2021-02-22 17:17:38

Proxy緩存代碼

2013-06-18 09:51:52

PomeloPomelo平臺搭建平臺

2024-04-24 11:42:21

Redis延遲消息數(shù)據(jù)庫

2014-07-29 09:44:58

jQuery源碼

2017-08-11 17:55:48

前端JavaScript模板引擎

2020-12-17 06:19:36

安全隱私個人信息

2021-05-19 14:22:46

代碼開發(fā)項(xiàng)目
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號