如何實現Virtual DOM

前言

前端開發者都知道,在網頁中瀏覽器資源開銷最大的便是DOM節點渲染了,DOM很慢並且非常龐大,網頁性能問題大多數都是由JavaScript修改DOM所引起的,網頁的重排、重繪都非常的消耗瀏覽器性能。重排、重繪的次數越多,我們的應用程序就會越卡頓。

DOM結構可以看作是從根節點開始的一個樹結構,那麼我們就可以通過JavaScript的對象來描述一個DOM,這就是虛擬DOM,JavaScript運行速度很快,虛擬DOM是放在JS 和 HTML中間的一個層。它可以通過新舊DOM的對比,來獲取對比之後的差異對象,然後有針對性的把差異部分真正地渲染到頁面上,從而減少實際DOM操作,最終達到性能優化的目的。

虛擬dom原理流程

簡單概括有三點:

  1. 用JavaScript模擬DOM樹,並渲染這個DOM樹
  2. 比較新老DOM樹,得到比較的差異對象
  3. 把差異對象應用到渲染的DOM樹。

流程如下圖所示:

本篇文章主要講述的是如何用JavaScript模擬DOM樹,並渲染這個DOM樹!

用JavaScript模擬DOM樹並渲染到頁面上

我們用JavaScript可以很容易的模擬一個DOM樹的結構,例如用這樣的一個構造函數createEl(tagName, props, children)來創建一個DOM結構。

  • tagName:標籤名
  • props:標籤上的屬性
  • children:子節點

那麼我們就以類的方式來創建吧,創建一個類,模擬vue的方式,基於一個根標籤構建整個dom樹:

class CreateEl {
      constructor(tagName, props, children) {
        // 當只有兩個參數tagName和children的時候,例如 celement(el, [123]) 做容錯處理
        if (Array.isArray(props)) {
          children = props
          props = {}
        }
        // tagName, props, children數據保存到this實例化對象上
        this.tagName = tagName
        this.props = props || {}
        this.children = children || []
        this.key = props ? props.key : undefined // 對於Vue的v-for,react的map循環,加key可以提高diff效率
        
        // 定義count在diff算法使用
        let count = 0
        this.children.forEach(child => {
          if (child instanceof CreateEl) {
            count += child.count
          } else {
            child = '' + child
          }
          count++
        })
        // 給每一個節點設置一個count
        this.count = count
      }
    }

這樣我們就創建好了一個創建DOM元素的類,實例化的對象中有傳入的標籤名、標籤綁定的屬性及子屬性。下面我們需要將這個實例化的對象轉化爲一個真實的DOM元素,所以我們需要給CreateEl類的原型上加一個 render 方法,代碼定義如下:

// 構建一個 dom 樹
    CreateEl.prototype.render = function () {
      // 創建dom
      const el = document.createElement(this.tagName)
      const props = this.props
      // 循環所有屬性,然後設置屬性
      for (let [key, val] of Object.entries(props)) {
        el.setAttribute(key, val)
      }
      this.children.forEach(child => {
        // 遞歸循環 構建dom tree,遞歸的終止條件:子節點爲文本節點
        let childEl = (child instanceof CreateEl) ? child.render() : document.createTextNode(child)
        el.appendChild(childEl)
      })
      return el
    }

render方法最終會返回一個完整的真實DOM Tree,它能直接通過 document.body.appendChild() 渲染到頁面上。我們再對CreateEl類進行一次封裝:

let h = (tagName, props, children) => {
      return new CreateEl(tagName, props, children)
    }

這個時候虛擬DOM就實現完成了,我們可以以JavaScript對象形式給標籤綁定各種屬性:style、event、class、id。下面我們就可以來試一試:

let a = h('ul', { id: 'ol-list' }, [
      h('li', { class: 'item1', style: "list-style: none" }, ['Item1']),
      h('li', { class: 'item2', id: 'li2', style: "color: pink", onclick: 'printStr ()' }, ['Item2']),
      h('li', { class: 'item3', onclick: 'alert(1234)' }, ['Item3'])
    ]).render()

// 供事件綁定的方法
function printStr() {
    alert('虛擬dom')
}
// 將DOM元素渲染到瀏覽器上
document.body.appendChild(a)

最後的結果就如下所示:

 點擊事件也能夠生效,到了這裏覺得原生JS真的有太多的可玩性了~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章