[vue]之虛擬dom -- diff

近段時間開始寫vue項目,以及在面試中遇到關於vue原理的問題,現打算開始看看Vue及全家桶的源碼,並記錄下本身的理解,若有錯誤,還望大佬們幫忙指出:vue

1.關於vue虛擬dom渲染到真實dom的簡單實現面試

// 虛擬DOM元素的類,構建實例對象,用來描述DOM
class Element {
  constructor(type, props, children) {
      this.type = type; //對應的dom元素名稱
      this.props = props; //對應的類名,style等等
      this.children = children; //對應的子元素
  }
}

// 建立虛擬DOM,返回虛擬節點(object)
function createElement(type, props, children) {
  return new Element(type, props, children);
}

// render方法將虛擬dom轉換爲真實dom
function render(domObj){
  //根據type渲染爲真實的dom元素
  let el = document.createElement(domObj.type)
  //遍歷自身props添加
  for(let key in domObj.props) {
    //設置屬性(只考慮了class,暫時沒有考慮到style樣式。。。)
    el.setAttribute(key, domObj.props[key])
  }
  //遍歷子元素
  domObj.children.forEach(child => {
     let chileEl = (child instanceof Element) ? render(child) : document.createTextNode(child)
     el.appendChild(chileEl)
  })
  return el;
}


//將元素dom插入頁面內
function renderDom(el,target){
  target.appendChild(el)
}


// 首先引入對應的方法來建立虛擬DOM
let virtualDom = createElement('ul', {class: 'list'}, [
    createElement('li', {class: 'item'}, ['子元素1']),
    createElement('li', {class: 'item'}, ['子元素2']),                                                                                                                                                                              
    createElement('li', {class: 'item'}, ['子元素3'])
]);
console.log(virtualDom)
let el = render(virtualDom);
console.log(el);
renderDom(el,document.getElementById('root'))

複製代碼

2.如何知道dom的變化(diff)?

比較兩棵DOM樹的差別是Virtual DOM算法最核心的部分.簡單的說就是新舊虛擬dom 的比較,若是有差別就以新的爲準,而後再插入的真實的dom中,從新渲染。 借網絡一張圖片說明:算法

Alt text
dom樹

如圖能夠看出,樹的渲染對比,須要通過新舊虛擬dom的對比,而且只在同層級進行比較: 經過第一步的虛擬dom簡單實現能夠看出,節點的對比主要爲如下四種狀況:bash

  • 此節點是否被移除 -> 添加新的節點網絡

  • 屬性是否被改變 -> 舊屬性改成新屬性app

  • 文本內容被改變-> 舊內容改成新內容dom

  • 節點要被整個替換 -> 結構徹底不相同 移除整個替換ui

如下是模仿節點被移除:this

/**
 * 變化虛擬dom
 */
//keyIndex記錄遍歷順序
let keyIndex = 0
// 遍歷
function diff(oldEle, newEle) {
    let patches = {}
    keyIndex = 0
    walk(patches, 0, oldEle, newEle)
    return patches
}
//分析變化
function walk(patches, index, oldEle, newEle) {
    let currentPatches = []
    //這裏應該有不少的判斷類型,這裏只處理了刪除的狀況...
    if (!newEle) {
        currentPatches.push({ type: 'remove' })
    }
    else if (oldEle.type == newEle.type) {
        //比較兒子們
        walkChild(patches, currentPatches, oldEle.children, newEle.children)
    }
    //判斷當前節點是否有改變,有的話把補丁放入補丁集合中
    if (currentPatches.length) {
        patches[index] = currentPatches
    }
}
function walkChild(patches, currentPatches, oldChilds, newChilds) {
    if (oldChilds) {
        for (let i = 0; i < oldChilds.length; i++) {
            let oldChild = oldChilds[i]
            let newChild = newChilds[i]
            walk(patches, ++keyIndex, oldChild, newChild)
        }
    }
}


/**
 * 將變化的補丁插入到真實dom
 */
let index = 0;
let allPatches;
function patcFunc(trueNode,patch){
  allPatches = patch
  showNow(trueNode)
}
//操做真實dom
function showNow(trueNode) {
  let currentPatches = allPatches[index]
  index++
  (trueNode.childNodes || []) && trueNode.childNodes.forEach(child => {
    showNow(child)
  })
  if (currentPatches) {
      doPatch(trueNode, currentPatches)
  }
}
//根據type是移除,則移除dom中的元素
function doPatch(ele, currentPatches) {
  currentPatches.forEach(currentPatch => {
      if (currentPatch.type == 'remove') {
          ele.parentNode.removeChild(ele)
      }
  })
}

// 首先引入對應的方法來建立虛擬DOM
let virtualDom = createElement('div', {class: 'list'}, [
    createElement('div', {class: 'item'}, ['周杰倫']),
    createElement('div', {class: 'item'}, ['林俊杰']),                                                                                                                                                                              
    createElement('div', {class: 'item'}, ['王力宏'])
]);
console.log('舊的dom', virtualDom)
//變化
let newDom = createElement('div', {class: 'list'}, [
  createElement('div', {class: 'item'}, ['林俊杰']),                                                                                                                                                                              
  createElement('div', {class: 'item'}, [''])
]);
let patch = diff(virtualDom,newDom)
let el = render(virtualDom);
patcFunc(el,patch)
renderDom(el,document.getElementById('root'))
複製代碼

總結的說,vue在建立虛擬dom的時候會以一個規定的格式來建立虛擬dom須要的數據,而後當發生改變的時候,新的虛擬dom的數據和舊的虛擬dom的數據會進行對比,按照層級進行對比(diff的比較方式,若是上一層的子節點不同,就直接替換,再也不比較子節點),不相同的便放到一個patch補丁裏,而後再將patch補丁裏的數據去操做當前真實dom,看是否節點須要改變,刪除等等。spa

相關文章
相關標籤/搜索