Vue原理-diff比对算法
Posted 火腿肠烧烤大赛冠军
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue原理-diff比对算法相关的知识,希望对你有一定的参考价值。
diff比对算法
源码版
https://blog.csdn.net/s2422617864/article/details/119855400
原理版
path函数
-
如果是同一个就会把真实的转化为虚拟的 如果不是则直接替换
-
将真实dom转化为虚拟dom形式
-
根据生成的新的虚拟dom生成新的节点 插入到页面中(注意此处涉及到父节点需要考虑子节点的遍历)[当结点不同时]
-
新节点与老节点类型相同时
- 新节点没有子节点–直接覆盖
- 新节点有子节点老节点没有 --直接覆盖
- 新的有老的也有–最复杂的情况要深度讨论
-
新的也有老的也有(以下6条规则每次对比都是从第一条开始对比 没匹配上则继续向下)
- 旧前和新前
- 匹配上则新旧指针同时向后++
- 未匹配则走2
- 旧后和新后
- 匹配上则指针同时向前
- 未匹配则走3
- 旧前和新后
- 匹配上则旧指针往后新指针向前
- 未匹配则走4
- 旧后和新前
- 匹配上则新指针往后旧指针向前
- 未匹配则走5
- 新的指针向后,将新的元素添加到页面上 所添加的元素如果旧的里面有则设置旧中的元素为undefined(这里有个操作如果是遇到undefined则继续向后查找)
- 创建或删除(创建没有的 删除多余的)
- 旧前和新前
首先:
- h函数用于生成虚拟节点,path比对新老虚拟节点之后替换真实dom树
- path算法替换新老节点 没有key暴力删除 有key按照key判断然后调整顺序(如果节点为同一节点致)
- 且path比对算法只能同层比较不能跨层比较
手写diff算法
整体代码
就是单纯将js转换为一个虚拟dom对象的形式(包含类型、内容、子节点、key等)
createElement.js
//vnode 为新节点,就是要创建的节点
export default function createElement( vnode )
//创建dom节点
let domNode = document.createElement( vnode.sel );
//判断有没有子节点 children 是不是为undefined
if( vnode.children == undefined )
domNode.innerText = vnode.text;
else if( Array.isArray(vnode.children) )//新的节点有children(子节点)
//说明内部有子节点 , 需要递归创建节点
for( let child of vnode.children)
let childDom = createElement(child);
domNode.appendChild( childDom );
//补充elm属性
vnode.elm = domNode;
return domNode;
patch.js
//oldVnode ===> 旧虚拟节点
//newVnode ===> 新虚拟节点
import vnode from './vnode';
import createElement from './createElement'
import patchVnode from './patchVnode'
export default function( oldVnode , newVnode )
//如果oldVnode 没有sel ,就证明是非虚拟节点 ( 就让他变成虚拟节点 )
if( oldVnode.sel == undefined )
oldVnode = vnode(
oldVnode.tagName.toLowerCase(), //sel
,//data
[],
undefined,
oldVnode
)
//判断 旧的虚拟节点 和 新的虚拟节点 是不是同一个节点
if( oldVnode.sel === newVnode.sel )
//判断就条件就复杂了(很多了)
patchVnode( oldVnode,newVnode );
else//不是同一个节点,那么就暴力删除旧的节点,创建插入新的节点。
//把新的虚拟节点 创建为 dom节点
let newVnodeElm = createElement( newVnode );
//获取旧的虚拟节点 .elm 就是真正节点
let oldVnodeElm = oldVnode.elm;
//创建新的节点
if( newVnodeElm )
oldVnodeElm.parentNode.insertBefore(newVnodeElm ,oldVnodeElm);
//删除旧节点
oldVnodeElm.parentNode.removeChild( oldVnodeElm );
patchVnode.js
import createElement from './createElement'
import updateChildren from './updateChildren'
export default function patchVnode( oldVnode,newVnode )
//判断新节点有没有children
if( newVnode.children === undefined ) //新的没有子节点
//新节点的文本 和 旧节点的文本内容是不是一样的
if( newVnode.text !== oldVnode.text )
oldVnode.elm.innerText = newVnode.text;
else//新的有子节点
//新的虚拟节点有 , 旧的虚拟节点有
if( oldVnode.children !== undefined && oldVnode.children.length > 0 )
//最复杂的情况了 diff核心了
updateChildren( oldVnode.elm , oldVnode.children , newVnode.children )
else//新的虚拟节点有 , 旧的虚拟节点“没有”
//把旧节点的内容 清空
oldVnode.elm.innerhtml = '';
//遍历新的 子节点 , 创建dom元素,添加到页面中
for( let child of newVnode.children )
let childDom = createElement(child);
oldVnode.elm.appendChild(childDom);
updateChildren.js
import patchVnode from './patchVnode'
import createElement from './createElement'
//判断倆个虚拟节点是否为同一个节点
function sameVnode( vNode1, vNode2 )
return vNode1.key == vNode2.key;
//参数一:真实dom节点
//参数二:旧的虚拟节点
//参数三:新的虚拟节点
export default ( parentElm , oldCh , newCh ) =>
let oldStartIdx = 0; //旧前的指针
let oldEndIdx = oldCh.length-1; //旧后的指针
let newStartIdx = 0; //新前的指针
let newEndIdx = newCh.length-1; //新后的指针
let oldStartVnode = oldCh[0]; //旧前虚拟节点
let oldEndVnode = oldCh[oldEndIdx]; //旧后虚拟节点
let newStartVnode = newCh[0]; //新前虚拟节点
let newEndVnode = newCh[newEndIdx]; //新后虚拟节点
while( oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx )
if( oldStartVnode == undefined )
oldStartVnode = oldCh[++oldStartIdx];
if( oldEndVnode == undefined )
oldEndVnode = oldCh[--oldEndVnode];
else if( sameVnode( oldStartVnode,newStartVnode ) )
//第一种情况:旧前 和 新前
console.log('1');
patchVnode( oldStartVnode,newStartVnode );
if( newStartVnode ) newStartVnode.elm = oldStartVnode?.elm;
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
else if( sameVnode( oldEndVnode,newEndVnode ) )
//第二种情况:旧后 和 新后
console.log('2');
patchVnode( oldEndVnode,newEndVnode );
if( newEndVnode ) newEndVnode.elm = oldEndVnode?.elm;
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
else if( sameVnode( oldStartVnode,newEndVnode ) )
//第三种情况:旧前 和 新后
console.log('3');
patchVnode( oldStartVnode,newEndVnode );
if( newEndVnode ) newEndVnode.elm = oldStartVnode?.elm;
//把旧前指定的节点移动到旧后指向的节点的后面
parentElm.insertBefore( oldStartVnode.elm , oldEndVnode.elm.nextSibling );
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
else if( sameVnode( oldEndVnode,newStartVnode ) )
//第四种情况:旧后 和 新前
console.log('4');
patchVnode( oldEndVnode,newStartVnode );
if( newStartVnode ) newStartVnode.elm = oldEndVnode?.elm;
//将旧后指定的节点移动到旧前指向的节点的前面
parentElm.insertBefore( oldEndVnode.elm , oldStartVnode.elm );
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
else
//第五种情况:以上都不满足条件 ===》查找
console.log('5');
//创建一个对象,存虚拟节点的(判断新旧有没有相同节点)
const keyMap = ;
for( let i=oldStartIdx;i<=oldEndIdx;i++)
const key = oldCh[i]?.key;
if( key ) keyMap[key] = i;
//在旧节点中寻找新前指向的节点
let idxInOld = keyMap[newStartVnode.key];
//如果有,说明数据在新旧虚拟节点中都存在
if( idxInOld )
const elmMove = oldCh[idxInOld];
patchVnode( elmMove,newStartVnode );
//处理过的节点,在旧虚拟节点的数组中,设置为undefined
oldCh[idxInOld] = undefined;
parentElm.insertBefore( elmMove.elm , oldStartVnode.elm );
else
//如果没有找到==》说明是一个新的节点【创建】
parentElm.insertBefore( createElement(newStartVnode) , oldStartVnode.elm );
//新数据(指针)+1
newStartVnode = newCh[++newStartIdx];
//结束while 只有俩种情况 (新增和删除)
//1. oldStartIdx > oldEndIdx
//2. newStartIdx > newEndIdx
if( oldStartIdx > oldEndIdx )
const before = newCh[newEndIdx+1] ? newCh[newEndIdx+1].elm : null;
for( let i=newStartIdx;i<=newEndIdx;i++)
parentElm.insertBefore( createElement(newCh[i]),before );
else
//进入删除操作
for( let i = oldStartIdx;i<=oldEndIdx;i++)
parentElm.removeChild(oldCh[i].elm);
h.js
import vnode from './vnode'
export default function( sel , data ,params )
//h函数的 第三个参数是字符串类型【意味着:他没有子元素】
if( typeof params =='string' )
return vnode( sel , data , undefined , params , undefined );
else if( Array.isArray(params) )//h函数的第三个参数,是不是数组,如果是数组【意味着:有子元素】
let children = [];
for( let item of params)
children.push(item);
return vnode( sel,data,children,undefined,undefined);
vnode.js
export default function( sel , data , children , text , elm )
let key = data.key;
return
sel,
data,
children,
text,
elm,
key
index.js
import h from './dom/h'
import patch from './dom/patch'
//获取到了真实的dom节点
let container = document.getElementById('container');
//获取到了按钮
let btn = document.getElementById('btn');
//虚拟节点
let vnode1 = h('ul',,[
h('li',key:'a','a'),
h('li',key:'b','b'),
h('li',key:'c','c'),
]);
patch( container,vnode1 );
let vnode2 = h('ul',,[
h('li',key:'a','a'),
h('li',key:'b','b'),
h('li',key:'c','c'),
h('li',key:'d','d'),
]);
btn.onclick = function()
patch( vnode1,vnode2 );
以上是关于Vue原理-diff比对算法的主要内容,如果未能解决你的问题,请参考以下文章