欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Vue中Virtual?DOM和Diff原理及實(shí)現(xiàn)

 更新時(shí)間:2023年03月21日 15:42:56   作者:NOxONE  
這篇文章主要為大家詳細(xì)介紹了Vue中Virtual?DOM和Diff原理及實(shí)現(xiàn)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下

0. 寫在開(kāi)頭

本文將秉承Talk is cheap, show me the code原則,做到文字最精簡(jiǎn),一切交由代碼說(shuō)明!

1. vdom

vdom即虛擬DOM,將DOM映射為JS對(duì)象,結(jié)合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
        }
      ]
    }
  ]
}

通過(guò)這個(gè)vdom實(shí)現(xiàn)簡(jiǎn)單的render函數(shù),可以通過(guò)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++ // 修改關(guān)聯(lián)數(shù)據(jù)
  render(data.arr) // 重新渲染:暴力刷新DOM,沒(méi)有diff,實(shí)際上只用更新最后一個(gè)div就行
}

使用snabbdom實(shí)現(xiàn)VDOM

snabbldom是簡(jiǎn)易實(shí)現(xiàn)vdom功能的庫(kù),有兩個(gè)核心api:h函數(shù)和patch函數(shù)

h(tag, attrs, children) // 創(chuàng)建vnode
patch(vnode, newVnode) // 對(duì)vnode進(jìn)行diff后掛載到真實(shí)dom上

結(jié)合hpatch實(shí)現(xiàn)render渲染函數(shù)

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++ // 修改關(guān)聯(lián)數(shù)據(jù)
  render(data.arr) // 重新渲染:在patch函數(shù)里經(jīng)過(guò)vdom的diff后再掛載到真實(shí)dom,這里只更新最后一個(gè)div
}

2. Diff

為了盡量減少DOM操作,需要通過(guò)diff對(duì)比新舊vnode,針對(duì)更改的地方進(jìn)行更新DOM,而非替換整個(gè)DOM

大體思路為:

  • 對(duì)新舊兩個(gè)節(jié)點(diǎn)調(diào)用patch函數(shù)
  • 進(jìn)來(lái)先判斷兩個(gè)節(jié)點(diǎn)是否為同一類型,具體是對(duì)比keytag、data等屬性
  • 若不為同一類型,那么基于新節(jié)點(diǎn)創(chuàng)建dom之后作替換
  • 若為同一類型,那么調(diào)用patchVnode函數(shù)
  • 進(jìn)來(lái)先判斷兩個(gè)節(jié)點(diǎn)是文本節(jié)點(diǎn)的話,那么就作文本內(nèi)容替換
  • 否則判斷是否都有子節(jié)點(diǎn),都有的話調(diào)用updateChildren函數(shù),通過(guò)首尾四個(gè)指針對(duì)子節(jié)點(diǎn)數(shù)組進(jìn)行diff更新;若舊節(jié)點(diǎn)有子節(jié)點(diǎn),新節(jié)點(diǎn)沒(méi)有,這時(shí)就刪除子節(jié)點(diǎn);若舊節(jié)點(diǎn)無(wú)子節(jié)點(diǎn),新節(jié)點(diǎn)有,這時(shí)基于新節(jié)點(diǎn)創(chuàng)建dom作替換即可

通過(guò)createElment函數(shù),將VDOM轉(zhuǎn)為真實(shí)DOM

function createElement(vnode) {
  if(vnode.text) return document.createTextNode(vnode) // 文本節(jié)點(diǎn)
    
  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
}

通過(guò)patch函數(shù),執(zhí)行diff更新操作

判斷vnodenewVnode是否為同一類型節(jié)點(diǎn),是則繼續(xù)遞歸對(duì)比子節(jié)點(diǎn),否則直接替換

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

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

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 // 獲取當(dāng)前舊節(jié)點(diǎn)對(duì)應(yīng)的dom,并賦值給新節(jié)點(diǎn)的el

  // 1.都為文本節(jié)點(diǎn),且文本不一樣
  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.都有子節(jié)點(diǎn),遞歸對(duì)比
  if (ch) return api.removeChild(el) // 3.vnode有子節(jié)點(diǎn),newVnode無(wú),刪除子節(jié)點(diǎn)
  return replaceVnode(vnode, newVnode) // 4. newNode有子節(jié)點(diǎn),vnode無(wú),替換即可
}

updateChildren實(shí)現(xiàn)比較復(fù)雜,使用首尾四指針進(jìn)行vnodenewVnode的對(duì)比

function updateChildren(el, ch, newCh) {
  // 子節(jié)點(diǎn)下標(biāo)
  let l = 0
  let r = ch.length - 1
  let newL = 0
  let newR = newCh.length - 1

  // 子節(jié)點(diǎn)
  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
    }
    
    // 新舊子節(jié)點(diǎn)首尾指針對(duì)比 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未知序列區(qū)間[l,r]生成key-idx的map表,用newLNode的key在未知序列中找到可復(fù)用的位置
    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
}

到此這篇關(guān)于淺析Vue中Virtual DOM和Diff原理及實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Vue Virtual DOM Diff內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue+axios+java實(shí)現(xiàn)文件上傳功能

    vue+axios+java實(shí)現(xiàn)文件上傳功能

    這篇文章主要為大家詳細(xì)介紹了vue+axios+java實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • undefined是否會(huì)變?yōu)閚ull原理解析

    undefined是否會(huì)變?yōu)閚ull原理解析

    這篇文章主要為大家介紹了undefined是否會(huì)變?yōu)閚ull原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • vue中引入第三方字體文件的方法示例

    vue中引入第三方字體文件的方法示例

    這篇文章主要介紹了vue中引入第三方字體文件的方法示例,文中講述了實(shí)現(xiàn)方法及其錯(cuò)誤的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • vue+element實(shí)現(xiàn)輸入密碼鎖屏

    vue+element實(shí)現(xiàn)輸入密碼鎖屏

    這篇文章主要為大家詳細(xì)介紹了vue+element實(shí)現(xiàn)輸入密碼鎖屏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • vue使用keep-alive如何實(shí)現(xiàn)多頁(yè)簽并支持強(qiáng)制刷新

    vue使用keep-alive如何實(shí)現(xiàn)多頁(yè)簽并支持強(qiáng)制刷新

    這篇文章主要介紹了vue使用keep-alive如何實(shí)現(xiàn)多頁(yè)簽并支持強(qiáng)制刷新,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 淺談vue的踩坑路

    淺談vue的踩坑路

    下面小編就為大家?guī)?lái)一篇淺談vue的踩坑路。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Vux+Axios攔截器增加loading的問(wèn)題及實(shí)現(xiàn)方法

    Vux+Axios攔截器增加loading的問(wèn)題及實(shí)現(xiàn)方法

    這篇文章主要介紹了Vux+Axios攔截器增加loading的問(wèn)題及實(shí)現(xiàn)方法,文中通過(guò)實(shí)例代碼介紹了vue中使用axios的相關(guān)知識(shí),需要的朋友可以參考下
    2018-11-11
  • 你準(zhǔn)備好迎接vue3.0了嗎

    你準(zhǔn)備好迎接vue3.0了嗎

    這篇文章主要介紹了你準(zhǔn)備好迎接vue3.0了嗎,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • vue中v-for循環(huán)選中點(diǎn)擊的元素并對(duì)該元素添加樣式操作

    vue中v-for循環(huán)選中點(diǎn)擊的元素并對(duì)該元素添加樣式操作

    這篇文章主要介紹了vue中v-for循環(huán)選中點(diǎn)擊的元素并對(duì)該元素添加樣式操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-07-07
  • vue遞歸獲取父元素的元素實(shí)例

    vue遞歸獲取父元素的元素實(shí)例

    這篇文章主要介紹了vue遞歸獲取父元素的元素實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08

最新評(píng)論