Vue原理-diff比对算法
2021/12/18 17:20:26
本文主要是介绍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比对算法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-16Vue3资料:新手入门必读教程
- 2024-11-16Vue3资料:新手入门全面指南
- 2024-11-16Vue资料:新手入门完全指南
- 2024-11-16Vue项目实战:新手入门指南
- 2024-11-16React Hooks之useEffect案例详解
- 2024-11-16useRef案例详解:React中的useRef使用教程
- 2024-11-16React Hooks之useState案例详解
- 2024-11-16Vue入门指南:从零开始搭建第一个Vue项目
- 2024-11-16Vue3学习:新手入门教程与实践指南
- 2024-11-16Vue3学习:从入门到初级实战教程