虚拟节点与DOM Diff算法


 DOM Diff

1.对比两个虚拟节点,找出差异,再对应到真实DOM ,进行补丁;

由于DOM操作损耗性能,所以应求得最小代价

2.遵循结构上一一对应关系,索引值

 3.p span 交换

 4.Diff——深度优先遍历

实现

createElement()方法 将虚拟节点变为对象

function createElement(type,props,children){
    return new Element(type,props,children);
}

 虚拟节点变为真实节点 渲染为DOM结构

render()

/vDOM到rDOM
function render (vDom){
    const {type,props,children}=vDom,
    el = document.createElement(type);

    for( let key in props){
        setAttrs( el,key, props[key]);
    }
    //console.log(el);

 等同于

开始渲染DOM

renderDOM()

function renderDOM(rDom,rootEl){
     rootEl.appendChild(rDom);

 }

domDiff.js

import {
    ATTR,
    TEXT,
    REPLACE,
    REMOVE
}from '.patchTypes';
//从当前解构

//声明
let patches ={},
    vnIndex = 0;


function domDiff(oldVDom,newVDom){
    let index = 0;
    vNodeWalk(oldVDom,newVDom,index);
    //虚拟节点遍历
    return patches;

}

function vNodeWalk(oldNode,newNode,index){
    let vnPatch = [];

    if(!newNode){
        vnPatch.push({
            type:REMOVE,
            index
        })
    }else if(typeof oldNode === 'string' && typeof newNode === 'string'){
        if(oldNode !== newNode){
            vnPatch.push({
                type:TEXT,
                text:newNode
            })
        }
    }
        else if(oldNode.type === newNode.type){
            const attrPatch = attrsWalk(oldNode.props,newNode.props);
            console.log(Object.keys(attrPatch));//一层 class

            if(Object.keys(attrPatch).length>0){
                vnPatch.push({
                    type:ATTR,
                    attrs:attrPatch
                });

            }
            //遍历子
            childrenWalk(oldNode.children,newNode.children);
            
        }
        else{
            vnPatch.push ({
                type:REPLACE,
                newNode
            });
        }

        //判断是否有patch
        if(vnPatch.length>0){
            patches[index] = vnPatch;
        }
    }
//对比props 
function attrsWalk( oldAttrs,newAttrs){

    let attrPatch={};
    //修改属性

    for (let key in oldAttrs){
        if(oldAttrs[key]!== newAttrs){
            attrPatch[key] = newAttrs[key];
            //打补丁
        }
    }
//是否有属性
    for (let key in newAttrs){
        //遍历新,对比旧判断是否有
        if(!oldAttrs.hasOwnProperty(key)){
            attrPatch[key] = newAttrs[key];
        }
    }

return attrPatch;

}

function childrenWalk(oldChildren, newChildren){
    oldChildren.map((c,idx) => {
        vNodeWalk(c,newChildren[idx], ++vnIndex)

    })
}
export default domDiff;

 实现打补丁

import {
  ATTR,
  TEXT,
  REPLACE,
  REMOVE
} from './js/patchTypes';
import { setAttrs, render } from './js/virtualDom';
import Element from './js/Element';

let finalPatches = {},
    rnIndex = 0;

function doPatch (rDom, patches) {
  finalPatches = patches;
  rNodeWalk(rDom);
}

function rNodeWalk (rNode) {
  const rnPatch = finalPatches[rnIndex ++],
        childNodes = rNode.childNodes;
  
  [...childNodes].map((c) => {
    rNodeWalk(c);
  });

  if (rnPatch) {
    patchAction(rNode, rnPatch);
  }
}

function patchAction (rNode, rnPatch) {
  rnPatch.map((p) => {
    switch (p.type) {
      case ATTR:
        for (let key in p.attrs) {
          const value = p.attrs[key];

          if (value) {
            setAttrs(rNode, key, value);
          } else {
            rNode.removeAttribute(key);
          }
        }
        break;
      case TEXT:
        rNode.textContent = p.text;
        break;
      case REPLACE:
        const newNode = (p.newNode instanceof Element)
                      ? render(p.newNode)
                      : document.createTextNode(p.newNode);
        
        rNode.parentNode.replaceChild(newNode, rNode);
        break;
      case REMOVE:
        rNode.parentNode.removeChild(rNode);
        break;
      default:
        break;
    }
  });
}

export default doPatch;

// vNode = virtual Node
// vnPatch = virtual Node patch
// rNode = real Node
// rnPatch = real Node patch