中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue中Virtual?DOM和Diff原理及實現方法是什么

發布時間:2023-03-21 16:46:19 來源:億速云 閱讀:121 作者:iii 欄目:開發技術

本篇內容介紹了“Vue中Virtual DOM和Diff原理及實現方法是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

1. vdom

vdom即虛擬DOM,將DOM映射為JS對象,結合diff算法更新DOM

以下為DOM

<div id="app">
  <div class="home">home</div>
</div>

映射成VDOM

{
  tag: 'div',
  attrs: {
    id: 'app'
  },
  children: [
    {
      tag: 'div',
      attrs: {
        class: 'home'
      },
      children: [
        {
          tag: undefined,
          attrs: undefined,
          text: 'home',
          children: undefined
        }
      ]
    }
  ]
}

通過這個vdom實現簡單的render函數,可以通過js操作修改dom

<template>
  <div id="app">
    <div v-for="item in arr">{{ item.name }} : {{ item.id }}</div>
  </div>
  <button id="btn">reRender</button>
</template>
let app = document.getElementById('app')
let data = {
  arr: [   
    { name: 'a', id: 1 },
    { name: 'b', id: 2 },
    { name: 'c', id: 3 },
  ]
}

function render(data) {
  app.innerHtml = ''
  let children = []
  data.forEach(item => {
    let el = document.createElement("div")
    el.innerHtml = `${ item.name } : ${item.id}`
    app.appendChild(el)
  })  
}

// test
render(data.arr) // 首次渲染
let btn = document.getElementById('btn')
btn.onClick = () => {
  data.arr[2].id++ // 修改關聯數據
  render(data.arr) // 重新渲染:暴力刷新DOM,沒有diff,實際上只用更新最后一個div就行
}

使用snabbdom實現VDOM

snabbldom是簡易實現vdom功能的庫,有兩個核心api:h函數和patch函數

h(tag, attrs, children) // 創建vnode
patch(vnode, newVnode) // 對vnode進行diff后掛載到真實dom上

結合hpatch實現render渲染函數

let app = document.getElementById('app')
let vnode;

function render(data) {
  let newVnode = h('div', { class: 'wrap' }, data.forEach(item => {
      return h('div', {}, `${item.name} : ${item.id}`)
    })
  )
  patch(vnode, newVnode)
  vnode = newVnode
}

render(data.arr) // 首次渲染

let btn = document.getElementById('btn')
btn.onClick = () => {
  data.arr[2].id++ // 修改關聯數據
  render(data.arr) // 重新渲染:在patch函數里經過vdom的diff后再掛載到真實dom,這里只更新最后一個div
}

2. Diff

為了盡量減少DOM操作,需要通過diff對比新舊vnode,針對更改的地方進行更新DOM,而非替換整個DOM

大體思路為:

  • 對新舊兩個節點調用patch函數

  • 進來先判斷兩個節點是否為同一類型,具體是對比keytagdata等屬性

  • 若不為同一類型,那么基于新節點創建dom之后作替換

  • 若為同一類型,那么調用patchVnode函數

  • 進來先判斷兩個節點是文本節點的話,那么就作文本內容替換

  • 否則判斷是否都有子節點,都有的話調用updateChildren函數,通過首尾四個指針對子節點數組進行diff更新;若舊節點有子節點,新節點沒有,這時就刪除子節點;若舊節點無子節點,新節點有,這時基于新節點創建dom作替換即可

通過createElment函數,將VDOM轉為真實DOM

function createElement(vnode) {
  if(vnode.text) return document.createTextNode(vnode) // 文本節點
    
  let { tag, attrs, children } = vnode
  
  let el = document.createElement(tag) // tag
  
  for(let key of attrs){ // attrs
    el.setAttribute(key, attrs[key])
  }
  
  children.forEach(childVnode => { // children
    el.appendChild(createElement(childVnode)) 
  })
  vnode.el = el
  return el
}

通過patch函數,執行diff更新操作

判斷vnodenewVnode是否為同一類型節點,是則繼續遞歸對比子節點,否則直接替換

function patch(vnode, newVnode) {
  if (isSameNode(vnode, newVnode)) patchVnode(vnode, newVnode)
  else replaceVnode(vnode, newVnode)
}

function replaceVnode(vnode, newVnode) {
  let el = vnode.el // 舊節點
  let parentEl = api.getParentNode(el) // 獲取父節點
  api.insertBefore(parentEl, createElement(newVnode), api.getNextSibling(el)) // 插入新節點
  api.removeChild(parentEl, el) // 刪除舊節點
}

function isSameNode(vnode, newVnode) {
  return (
    vnode.key == newVnode.key && // key是否相同
    vnode.tag == newVnode.tag && // tag是否相同
    isDef(vnode.data) == isDef(newVnode.data) // 是否都定義了data
    // &&... 其他條件
  )
}

function patchVnode(vnode, newVnode) {
  let el = newVnode.el = vnode.el // 獲取當前舊節點對應的dom,并賦值給新節點的el

  // 1.都為文本節點,且文本不一樣
  if (vnode.text && newVnode.text && vnode.text != newVnode.text)
    return api.setElText(el, newVnode.text) // 替換文本
  
  let ch = vnode.children
  let newCh = newVnode.children
  if (ch && newCh) return updateChildren(el, ch, newCh) // 2.都有子節點,遞歸對比
  if (ch) return api.removeChild(el) // 3.vnode有子節點,newVnode無,刪除子節點
  return replaceVnode(vnode, newVnode) // 4. newNode有子節點,vnode無,替換即可
}

updateChildren實現比較復雜,使用首尾四指針進行vnodenewVnode的對比

function updateChildren(el, ch, newCh) {
  // 子節點下標
  let l = 0
  let r = ch.length - 1
  let newL = 0
  let newR = newCh.length - 1

  // 子節點
  let lNode = ch[l]
  let rNode = ch[r]
  let newLNode = newCh[newL]
  let newRNode = newCh[newR]

  while (l <= r && newL <= newR) {
    if (!lNode || !rNode || !newLNode || !newRNode) { // 邊界處理
      if (!lNode) lNode = ch[++l]
      if (!rNode) rNode = ch[--r]
      if (!newLNode) newLNode = newCh[++newL]
      if (!newRNode) newRNode = newCh[--newR]
      continue
    }
    
    // 新舊子節點首尾指針對比 l*newL、r*newR、l*newR、r*newL
    if (isSameNode(lNode, newLNode)) {
      patchVnode(lNode, newLNode)
      lNode = ch[++l]
      newLNode = newCh[++newL]
      continue
    }
    if (isSameNode(rNode, newRNode)) {
      patchVnode(rNode, newRNode)
      rNode = ch[--r]
      newRNode = newCh[--newR]
      continue
    }
    if (isSameNode(lNode, newRNode)) {
      patchVnode(lNode, newRNode)
      api.insertBefore(el, lNode.el, api.nextSibling(rNode.el))
      lNode = ch[++l]
      newRNode = newCh[--newR]
      continue
    }
    if (isSameNode(rNode, newLNode)) {
      patchVnode(rNode, newLNode)
      api.insertBefore(el, rNode.el, lNode.el)
      rNode = ch[--r]
      newLNode = newCh[++newL]
      continue
    }

    // 在vnode未知序列區間[l,r]生成key-idx的map表,用newLNode的key在未知序列中找到可復用的位置
    if (!keyIdxMap) keyIdxMap = getKeyIdxMap(ch, l, r) // map

    keyIdx = keyIdxMap.get(newLNode.key)
    if (!keyIdx) {
      api.insertBefore(el, createElement(newLNode), lNode.el)
    }
    else {
      let nodeToMove = ch[keyIdx]
      patchVnode(nodeToMove, newLNode)
      api.insertBefore(el, nodeToMove.el, lNode.el)
    }
    newLNode = newCh[++newL]
  }
}

function getKeyIdxMap(ch, l, r) {
  let map = new Map()
  while (l <= r) map.set(ch[l].key, l++)
  return map
}

“Vue中Virtual DOM和Diff原理及實現方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節
推薦閱讀:
  1. vue
  2. vue分頁效果

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宁明县| 东乡| 亳州市| 双峰县| 轮台县| 寿宁县| 沙坪坝区| 密云县| 郑州市| 顺昌县| 平江县| 丰城市| 聂荣县| 嘉黎县| 固始县| 龙川县| 郧西县| 侯马市| 陕西省| 夹江县| 广饶县| 弥勒县| 舒城县| 景泰县| 平武县| 澜沧| 博罗县| 迁西县| 珲春市| 白水县| 汾西县| 涟水县| 莱州市| 济源市| 通城县| 桂阳县| 乐昌市| 许昌县| 崇州市| 微山县| 台安县|