vue domdiff实现原理

domdiff

index.js

整合所有方法,并导出
h:将一个节点渲染为一个虚拟dom节点
render:更具虚拟dom节点创建真实动漫节点,并添加到盒子中去
patch:新老元素比对

import h from './h';
import {render,patch} from './patch';
export {
  h,
  render,
  patch
}

vnode.js

export default function vnode(type,key,props,children,text) {
  return {
    type,
    key,
    props,
    children,
    text
  }
}

h.js

import vnode from './vnode';
export default function createElement(type,props={},...children) {
  let key;
  if(props.key){
    key = props.key;
    delete props.key;
  }
  children = children.map(child=>{
    if(typeof child === 'string'){
      return vnode(undefined,undefined,undefined,undefined,child);
    }else{
      return child
    }
  });
  return vnode(type,key,props,children);
}

patch.js

export function render(vnode,container) {
  let ele = createDomElementFromVnode(vnode);
  container.appendChild(ele);
}
function createDomElementFromVnode(vnode){
  let {type,key,props,children,text}  = vnode;
  if(type){//元素
    vnode.domElement = document.createElement(type);
    //根据当前的虚拟dom节点渲染真实dom元素
    updateProperties(vnode);
    //children中也放的虚拟节点vnode 将儿子vnode渲染到父亲上 递归
    children.forEach(childVnode=>render(childVnode,vnode.domElement));
  }else{//文本
    vnode.domElement = document.createTextNode(text);

  }
  return vnode.domElement;
}
//后续会根据老属性对比新属性重新更新节点
function updateProperties(newVnode,oldProps={}) {
  let domElement = newVnode.domElement;
  let newProps = newVnode.props;

  //如果老属性中没有 新属性中有  则需在真实dom中移除这些属性
  for(let oldPropName in oldProps){
    if(!newProps[oldPropName]){
      delete domElement[oldPropName];
    }
  }
  
  //比对styleObj 老的中有 新的中没有某个性 则需设置为‘’
  let newStyleObj = newProps.style || {};
  let oldStyleObj = domElement.style || {};
  for(let propName in oldStyleObj){
    if(!newStyleObj[propName]){
      domElement.style.setProperty(propName,'');
    }
  }
  //如果老属性中有 新属性中有 则需覆盖老属性
  for(let newPropName in newProps ){
    if(newPropName === 'style'){
      let styleObj = newProps.style;
      for(let s in styleObj){
        domElement.style[s] = styleObj[s];
      }
    }else{
      domElement[newPropName] = newProps[newPropName];
    }
  }
}




function isSameVnode(oldVnode,newVnode) {
  return oldVnode.key === newVnode.key && oldVnode.type === newVnode.type;
}

function createMapByIndex(oldChildren){
  let map = {};
  for(let i = 0;i<oldChildren.length;i++){
    let current = oldChildren[i];
    if(current.key){
      map[current.key] = i;
    }
  }
  return map;
}
// 节点替换比对
export function patch(oldVnode,newVnode){
  // 类型不同
  if(oldVnode.type !== newVnode.type){
    return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode),oldVnode.domElement);
  }
  //类型相同 并且是文本
  if(newVnode.text){
    return oldVnode.domElement.textContent = newVnode.text
  }
  //类型相同 并且是标签
  let domElement = newVnode.domElement = oldVnode.domElement;//复用老的domElement,然后更新属性,和儿子节点
  
  updateProperties(newVnode,oldVnode.props); //比属性

  //比儿子
  let oldChildren = oldVnode.children;
  let newChildren = newVnode.children;
  if(oldChildren.length > 0 && newChildren.length > 0){
    updateChildren(oldVnode.domElement,oldChildren,newChildren);
  }else if(oldChildren.length > 0){
    domElement.innerHTML = '';
  }else if(newChildren.length > 0){
    for(let i = 0;i < newChildren.length;i++){
      domElement.appendChild(createDomElementFromVnode(newChildren[i]));
    }
  }
}
function updateChildren(parent,oldChildren,newChildren) {
  let oldStartIndex = 0;
  let oldStartVnode = oldChildren[0];  
  let oldEndIndex = oldChildren.length-1;
  let oldEndVnode = oldChildren[oldEndIndex];
  let map = createMapByIndex(oldChildren);

  let newStartIndex = 0;
  let newStartVnode = newChildren[0];  
  let newEndIndex = newChildren.length-1;
  let newEndVnode = newChildren[newEndIndex];

  //判断老孩子和新孩子,谁结束就停止循环
  while(oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex){
    if(!oldStartVnode){
      oldStartVnode = oldChildren[++oldStartIndex];
    }else if(!oldEndVnode){
      oldEndVnode = oldChildren[--oldEndIndex];
    }else
    if(isSameVnode(oldStartVnode,newStartVnode)){// 先比较头和头
      patch(oldStartVnode,newStartVnode);
      oldStartVnode = oldChildren[++oldStartIndex];
      newStartVnode = newChildren[++newStartIndex];
    }else if(isSameVnode(oldEndVnode,newEndVnode)){// 比较尾和尾
      patch(oldEndVnode,newEndVnode);
      oldEndVnode = oldChildren[--oldEndIndex];
      newEndVnode = newChildren[--newEndIndex];
    }else if(isSameVnode(oldStartVnode,newEndVnode)){//比较头和尾
      patch(oldStartVnode,newEndVnode); //复用老头

      parent.insertBefore(oldStartVnode.domElement,oldEndVnode.domElement.nextSibling); //移动老头 到 尾部
      oldStartVnode = oldChildren[++oldStartIndex];
      newEndVnode = newChildren[--newEndIndex];
    }else if(isSameVnode(oldEndVnode,newStartVnode)){//比较尾和头
      patch(oldEndVnode,newStartVnode); //复用老尾
      parent.insertBefore(oldEndVnode.domElement,oldStartVnode.domElement); //移动老尾 到 头部
      oldEndVnode = oldChildren[--oldEndIndex];
      newStartVnode = newChildren[++newStartIndex];
    }else{ //头尾都不一样 暴力对比
      let index = map[newStartVnode.key];
      if(index === undefined){
        parent.insertBefore(createDomElementFromVnode(newStartVnode),oldStartVnode.domElement);

      }else{
        let toMoveNode = oldChildren[index];
        patch(toMoveNode,newStartVnode);
        parent.insertBefore(toMoveNode.domElement,oldStartVnode.domElement);
        oldChildren[index] = undefined;
      }
      newStartVnode = newChildren[++newStartIndex];
    }
  }
  //到这里,如果还小于等于 则新的有剩余
  if(newStartIndex <= newEndIndex){
    for(let i = newStartIndex;i <= newEndIndex;i++){
      let beforeElement = newChildren[newEndIndex + 1] == null?null:newChildren[newEndIndex + 1].domElement;
      parent.insertBefore(createDomElementFromVnode(newChildren[i]),beforeElement);
    }
  }
  //如果老的还有剩余,则移除
  if(oldStartIndex <= oldEndIndex){
    for(let i = oldStartIndex ;i <= oldEndIndex;i++){
      if(oldChildren[i]){
        parent.removeChild(oldChildren[i].domElement);
      }
    }
  }

}

test index.js

/**
 * 实现步骤:
 * 
 * 当使用h函数渲染的时候,默认将其渲染为虚拟dom节点:
 *虚拟dom就是一个对象,来描述dom节点 jsx
{
  type:'div',
  props:{id:'wrapper',a:1},
  children:[
    {type:'span',props:{style:{color:'red'}},children:[text:'hello']},
    {type:'',props:'',children:[],text:'gmh'}
  ]
}
 * 有了虚拟dom节点之后,根据render方法将虚拟dom节点渲染为真实dom元素,插入到app盒子中
<div id="wrapper" a=1>
  <span style:"color:red;">hello</span>
  gmh
</div>
 * 当要添加新的节点的时候,通过对老的虚拟节点和新的虚拟节点进行前后比对patch,比对后再添加
 */

import {h,render,patch} from './domdiff';
// let vnode = h('div',{id:'wrapper',a:1,key:'xx'},h('span',{style:{color:'red'}},'hello'),'gmh');

// render(vnode,app);

//patch比对
// 类型不同
// let newVnode = h('h1',{id:'b'},'haha');
// patch(vnode,newVnode);

//类型相同 并且都是标签
// let newVnode = h('div',{style:{color:'blue'}},'haha');
// patch(vnode,newVnode);

//老的有儿子 新的没儿子
// let newVnode = h('div',{style:{color:'blue'}});
// setTimeout(()=>{
//   patch(vnode,newVnode);
// },1000);

//老的没儿子 新的有儿子
// let oldVnode = h('div',{id:'wrapper',a:1,key:'xx'},h('span',{style:{color:'red'}},'hello'));
// let newVnode = h('div',{style:{color:'blue'}},'haha');
// setTimeout(()=>{
//   patch(oldVnode,newVnode);
// },1000);

let oldVnode = h('div',{},
  h('li',{style:{background:'blue'},key:'A'},'A'),
  h('li',{style:{background:'yello'},key:'B'},'B'),
  h('li',{style:{background:'green'},key:'C'},'C'),
  h('li',{style:{background:'orange'},key:'D'},'D'),
  h('li',{style:{background:'white'},key:'I'},'I'),

);
render(oldVnode,app);
let newVnode = h('div',{},
  h('li',{style:{background:'black'},key:'E'},'E1'),
  h('li',{style:{background:'orange'},key:'C'},'C1'),
  h('li',{style:{background:'blue'},key:'D'},'D1'),
  h('li',{style:{background:'black'},key:'F'},'F1'),
  h('li',{style:{background:'yello'},key:'A'},'A1'),
  h('li',{style:{background:'green'},key:'B'},'B1'),
  h('li',{style:{background:'black'},key:'G'},'G1'),
);
setTimeout(()=>{
  patch(oldVnode,newVnode);
},2000);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vuediff算法是通过比较新旧虚拟节点来确定需要更新的DOM元素,以提高性能和效率。首先,Vue会将新旧节点都转换为虚拟节点的形式,然后采用同层对比的方式进行比较,即只比较相同层级的节点。只有当这些节点完全相同时,才会进一步比较它们的子节点,并进行核心的diff算法操作。 在比较新旧子节点时,Vue使用了双指针的方案。这意味着在新旧虚拟节点的头部和尾部都会有一个指针,用来标记它们的位置。通过双指针的方式,Vue可以高效地定位到需要更新的节点并进行相应的操作。 具体实现的代码如下所示,代码中包含了四个指针的索引以及它们对应的虚拟节点。通过这些指针,Vue可以在比较过程中快速定位到需要更新的节点,从而实现diff算法的原理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [详解vuediff算法原理](https://download.csdn.net/download/weixin_38617615/12759506)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Vue2源码-diff算法详解](https://blog.csdn.net/weixin_46831501/article/details/126031613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值