深入浅出Vue.js(二) 虚拟DOM & diff算法

Lophole 2020-06-10

VirtualDom

vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。

这是Vue中vnode实例对象中设计包含的所有属性和方法

class VNode {
  constructor(tag,data,children,text,elm,context,componentOptions,asyncFactory){
    this.tag = tag //节点名称 eg:p、ul、div
    this.data = data //节点上的数据 eg:attrs、class、style
    this.children = children //当前节点的子节点列表
    this.text = text //文本
    this.elm = elm //vnode对应的真实的dom节点
    this.ns = undefined //
    this.context = context //当前组件的实例
    this.functionalContext = undefined //
    this.functionalOptions = undefined //
    this.functionalScopeId = undefined //
    this.key = data && data.key //vnode标记 在diff过程中提高diff效率
    this.componentOptions = componentOptions //组件节点的选项参数 eg:propsData、tag、children
    this.componentInstance = undefined //组件的实例
    this.parent = undefined //
    this.raw = false //
    this.isStatic = false //
    this.isRootInsert = false //
    this.isComment = false // 是否为注释
    this.isCloned = false // 是否为克隆
    this.isOnce = false //
    this.asyncFactory = asyncFactory //
    this.asyncMeta = undefined //
    this.isAsyncPlaceholder = false //
  }
  getChild(){
    return this.componentInstance
  }
}

VirtualDom 简单实现demo

class Element {
  constructor(type,props,children){
    this.type = type
    this.props = props
    this.children = children
  }
}
function createElement(type,props,children){
  return new Element(type,props,children)
}


/**
* 渲染虚拟dom
*/
function render(domObj){
  let el = document.createElement(domObj.type)
  for(let key in domObj.props){
    setAttr(el,key,domObj.props[key])
  }
  domObj.children.forEach((child)=>{
    child = (child instanceof Element) ? render(child) : document.createTextNode(child)
    el.appendChild(child)
  })
  return el
}


/**
* setAttr方法实现
*/
function setAttr(node,key,value){
  switch(key){
    case ‘value‘:
      if(node.tagName.toLowerCase() == ‘input‘ || node.tagName.toLowerCase() == ‘textarea‘){
        node.value = value
      }else{
        node.setAttribute(key,value)
      }
      break;
    case ‘style‘:
      node.style.cssText = value
      break;
    default:
      node.setAttribute(key,value)
      break;
  }
}


/**
* 渲染dom方法
*/
function renderDom(el,target){
  target.appendChild(el)
}


// 执行以下看看效果
// 像不像render函数的写法 哈哈哈
let vNode = createElement(‘ul‘,{class:‘list‘},[
  createElement(‘li‘,{class:‘item‘},[‘乔丹‘]),
  createElement(‘li‘,{class:‘item‘},[‘科比‘]),
  createElement(‘li‘,{class:‘item‘},[‘韦德‘])
]);
let el = render(vNode)
let root = document.getElementById(‘zjy‘)
renderDom(el,root)

diff算法 简单实现

//判断是否为string
function isString(str){
  return typeof str === ‘string‘
}


//不同attr
function diffAttr(oldAttr,newAttr){
  let patch = {}
  for(let key in oldAttr){
    if(oldAttr[key] !== newAttr[key]){
      patch[key] = newAttr[key]
    }
  }
  for(let key in newAttr){
    if(!oldAttr.hasOwnProperty(key)){
      patch[key] = newAttr[key]
    }
  }
  return patch
}


//不同children
let num = 0
function diffChildren(oldChildren,newChildren,patches){
  oldChildren.forEach((child,index)=>{
    walk(child,newChildren[index],++num,patches)
  })
}


function diff(oldTree,newTree){
  let patches = {} //存放补丁的对象
  let index = 0
  walk(oldTree,newTree,index,patches)
  return patches
}


/**
* walk方法中diff的情况相当于只是列举了几种情况,我在控制台执行了下,的确有的情况没有体现出来,例如删除 新增子节点,
* 所以下面的patch补丁方法中进行补丁的类别完全是按照diff中创在的不同type类型执行的不同操作
*/
function walk(oldNode,newNode,index,patches){
  let current = []
  if(!newNode){
    current.push({type:‘REMOVE‘,index})
  }else if(isString(oldNode) && isString(newNode)){
    if(oldNode !== newNode){
      current.push({tyep:‘TEXT‘,text:newNode})
    }
  }else if(oldNode.type === newNode.type){
    let attr = diffAttr(oldNode.props,newNode.props)
    if(Object.keys(attr).length > 0){
      current.push({type:‘ATTR‘,attr})
    }
    diffChildren(oldNode.children,newNode.children,patches)
  }else{
    current.push({type:‘REPLACE‘,newNode})
  }
  if(current.length){
    patches[index] = current
  }
}


/**
* patch补丁(patch补丁简单实现的原由是因为diff方法简单实现造成的,有兴趣的可以去了解下vue中实现的diff算法 超长~~)
*/
let allPatches
let index = 0
function patch(node,patches){
  allPatches = patches
  walk(node)
}
function walk(node){
  let current = allPatches[index++]
  let childNodes = node.childNodes
  childNodes.forEach((child)=>{
    walk(child)
  })
  if(current){
    doPatch(node,current)
  }
}
function doPatch(node,patches){
  patches.forEach((patch)=>{
    switch(patch.type){
      case ‘ATTR‘:
        for(let key in patch.attr){
          let value = patch.attr[key]
          if(value){
            setAttr(node,key,value)
          }else{
            node.removeAttribute(key)
          }
        }
        break;
      case ‘TEXT‘:
        node.textContent = patch.text
        break;
      case ‘REPLACE‘:
        let newNode = patch.newNode
        newNode = (newNode instanceof Element) ? render(newNode) : document.createTextNode(newNode)
        node.parentNode.replaceChild(newNode,node)
        break;
      case ‘REMOVE‘:
        node.parentNode.removeChild(node)
        break;
      default:
        break;
    }
  })
}

 

相关推荐