[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

相关文章
相关标签/搜索