首页 > 编程语言 >Vue2中Diff算法解析

Vue2中Diff算法解析

时间:2024-07-18 23:54:53浏览次数:12  
标签:el key 算法 oldVnode let Vue2 newChildren Diff oldChildren

Vue2中Diff算法解析

import {compileToFunction} from './compiler/index.js';
import { patch,createElm } from './vdom/patch';
// 1.创建第一个虚拟节点
let vm1 = new Vue({data:{name:'hs'}});
let render1 = compileToFunction('<div>{{name}}</div>')
let oldVnode = render1.call(vm1)
// 2.创建第二个虚拟节点
let vm2 = new Vue({data:{name:'hh'}});
let render2 = compileToFunction('<p>{{name}}</p>');
let newVnode = render2.call(vm2);
// 3.通过第一个虚拟节点做首次渲染
let el = createElm(oldVnode)
document.body.appendChild(el);
// 4.调用patch方法进行对比操作
patch(oldVnode,newVnode);

vue中的diff算法就先构建出两个虚拟dom 之后做patch

image-20240715202703653

基本Diff算法

比对标签

 // 如果标签不一致说明是两个不同元素
 if(oldVnode.tag !== vnode.tag){
    oldVnode.el.parentNode.replaceChild(createElm(vnode),oldVnode.el)
 }

在diff过程中会先比较标签是否一致,如果标签不一致用新的标签替换掉老的标签

// 如果标签一致但是不存在则是文本节点
if(!oldVnode.tag){
    if(oldVnode.text !== vnode.text){
    	oldVnode.el.textContent = vnode.text;
    }
}

如果标签一致,有可能都是文本节点,那就比较文本的内容即可

比对属性

如果标签一样,并且需要开始比对标签的属性和儿子

// 复用标签,并且更新属性
let el = vnode.el = oldVnode.el;
// 更新属性,用新的虚拟节点的属性和老的比较,去更新节点
updateProperties(vnode,oldVnode.data);
function updateProperties(vnode,oldProps={}) {
    let newProps = vnode.data || {};
    let el = vnode.el;
    // 比对样式
    let newStyle = newProps.style || {};
    let oldStyle = oldProps.style || {};
    // 老的样式中有 新的没有 删除老的样式
    for(let key in oldStyle){
        if(!newStyle[key]){
            el.style[key] = ''
        }
    }
    // 老的有 新的没有 删除多余属性
    for(let key in oldProps){
        if(!newProps[key]){
            el.removeAttribute(key);
        }
    }
    for (let key in newProps) {
        if (key === 'style') {
            for (let styleName in newProps.style) {
                el.style[styleName] = newProps.style[styleName];
            }
        } else if (key === 'class') {
            el.className = newProps.class;
        } else {
            el.setAttribute(key, newProps[key]);
        }
    }
}

当标签相同时,我们可以复用老的标签元素,并且进行属性的比对。

比对子元素

/ 比较孩子节点
let oldChildren = oldVnode.children || [];
let newChildren = vnode.children || [];
// 新老都有需要比对儿子
if(oldChildren.length > 0 && newChildren.length > 0){
	
// 老的有儿子  新的没有清空即可
}else if(oldChildren.length > 0 ){
	el.innerHTML = '';
// 新的有儿子
}else if(newChildren.length > 0){
	for(let i = 0 ; i < newChildren.length ;i++){
		let child = newChildren[i];
		el.appendChild(createElm(child));
	}
}

这里要判断新老节点儿子的状况

if (oldChildren.length > 0 && newChildren.length > 0) {
	updateChildren(el, oldChildren, newChildren)
	// 老的有儿子新的没有清空即可
}

Diff中的优化策略

Vue2中采用了双指针的方式

在开头和结尾新增元素

做一个循环,同时需纳入老的和新的,哪个先结束循环就停止,将多余的删除或者添加进去

在头部创建开头指针,每次比较后后移

在尾部创建尾部指针,每次比较后前移

function isSameVnode(oldVnode,newVnode){
    // 如果两个人的标签和key 一样我认为是同一个节点 虚拟节点一样我就可以复用真实节点了
    return (oldVnode.tag === newVnode.tag) && (oldVnode.key === newVnode.key)
}
function updateChildren(parent, oldChildren, newChildren) {
    let oldStartIndex = 0;
    let oldStartVnode = oldChildren[0];
    let oldEndIndex = oldChildren.length - 1;
    let oldEndVnode = oldChildren[oldEndIndex];

    let newStartIndex = 0;
    let newStartVnode = newChildren[0];
    let newEndIndex = newChildren.length - 1;
    let newEndVnode = newChildren[newEndIndex];
    // 老的和老的比,新的和新的比,比较水先循环停止
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        // 优化向后追加逻辑
        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];
        }
    }
    // 开始和结束重合了
    if(newStartIndex <= newEndIndex){
        for(let i = newStartIndex ; i<=newEndIndex ;i++){
            let ele = newChildren[newEndIndex+1] == null? null:newChildren[newEndIndex+1].el;
            // 将新的多余的插入进去即可
            parent.insertBefore(createElm(newChildren[i]),ele);
        }
    }
}

头移尾、尾移头

// 头移动到尾部
else if(isSameVnode(oldStartVnode,newEndVnode)){
    patch(oldStartVnode,newEndVnode);
    parent.insertBefore(oldStartVnode.el,oldEndVnode.el.nextSibling);
    oldStartVnode = oldChildren[++oldStartIndex];
    newEndVnode = newChildren[--newEndIndex]
// 尾部移动到头部
}else if(isSameVnode(oldEndVnode,newStartVnode)){
    patch(oldEndVnode,newStartVnode);
    parent.insertBefore(oldEndVnode.el,oldStartVnode.el);
    oldEndVnode = oldChildren[--oldEndIndex];
    newStartVnode = newChildren[++newStartIndex]
}

以上四个条件对常见的dom操作进行了优化。

移到大于修改,循环的时候不能用index作为key

暴力比对

儿子之前没有关系,则进行暴力比对

// 把老的做成映射表
function makeIndexByKey(children) {
    let map = {};
    children.forEach((item, index) => {
    	map[item.key] = index
    });
    return map; 
}
let map = makeIndexByKey(oldChildren);

对所有的孩子元素进行编号

// 拿到开头的虚拟节点的key 去老的中找
let moveIndex = map[newStartVnode.key];
if (moveIndex == undefined) { // 老的中没有将新元素插入
    parent.insertBefore(createElm(newStartVnode), oldStartVnode.el);
} else { // 有的话做移动操作
    let moveVnode = oldChildren[moveIndex]; 
    oldChildren[moveIndex] = undefined;
    parent.insertBefore(moveVnode.el, oldStartVnode.el);
    // 比较属性和儿子
    patch(moveVnode, newStartVnode);
}
// 用新的不停的去老的里面找
newStartVnode = newChildren[++newStartIndex]

用新的元素去老的中进行查找,如果找到则移动,找不到则直接插入

if(oldStartIndex <= oldEndIndex){
    for(let i = oldStartIndex; i<=oldEndIndex;i++){
        let child = oldChildren[i];
        if(child != undefined){
            parent.removeChild(child.el)
        }
    }
}

如果有剩余则直接删除

if(!oldStartVnode){
    oldStartVnode = oldChildren[++oldStartIndex];
}else if(!oldEndVnode){
    oldEndVnode = oldChildren[--oldEndIndex]
}

在比对过程中,可能出现空值情况则直接跳过

标签:el,key,算法,oldVnode,let,Vue2,newChildren,Diff,oldChildren
From: https://blog.csdn.net/qq_40588441/article/details/140536006

相关文章

  • [深入理解Java虚拟机]Hotspot垃圾回收算法
    HotSpot的算法细节实现3.2、3.3节从理论原理上介绍了常见的对象存活判定算法和垃圾收集算法,Java虚拟机实现这些算法时,必须对算法的执行效率有严格的考量,才能保证虚拟机高效运行。本章设置这部分内容主要是为了稍后介绍各款垃圾收集器时做前置知识铺垫,如果读者对这部分内容感到枯......
  • Visible and Clear: Finding Tiny Objects in Difference Map
    VisibleandClear:FindingTinyObjectsinDifferenceMap论文链接:https://arxiv.org/abs/2405.11276项目链接:https://github.com/Hiyuur/SR-TOD(ECCV2024)Abstract微小目标检测是目标检测领域的关键问题之一。大多数通用检测器的性能在微小目标检测任务中显著下降......
  • VUE diff 算法:为了直观展示,画了一张图来直观展示
      上图直观展示了Vue的Diff算法流程:3种方式比较根节点:图中左侧的"OldVNode"和右侧的"NewVNode"表示旧的和新的虚拟DOM根节点。箭头表示比较过程,如果根节点不同,直接替换整个节点。比较子节点:当根节点相同时,递归比较子节点。左侧"OldChild1"和"O......
  • 算法竞赛复健记录
    高三学了一年文化课感觉已经不会算法竞赛了,开个博客记录一下复健历程。CF1662F题意:有\(n\le200000\)个点,每个点有能量\(p_i\),消息能从\(i\)传到\(j\)当且仅当\(|i-j|\le\min(p_i,p_j)\),求消息从\(a\)点传到\(b\)点至少需要经过几个点。考虑把点按\(p_i\)......
  • WPF The calling thread cannot access this object because a different thread owns
      publicintImgIdx{get{returnimgIdx;}set{if(value!=imgIdx){imgIdx=value;if(imgIdx<0){imgIdx=imgsCount-1;......
  • 杂谈:Vue 的 Diff 算法
    Vue.js使用虚拟DOM来高效地更新用户界面,其中的Diff算法是关键。Diff算法负责找出新旧虚拟DOM之间的差异,并高效地更新实际DOM。本文将详细解析Vue的Diff算法的工作原理和在实际开发中的应用。1.什么是虚拟DOM虚拟DOM是一个轻量级的JavaScript对象,用于描述DOM......
  • 密钥算法测试
    1.RASpropertiesimporthuksfrom'@ohos.security.huks';functionGetRSA4096GenerateProperties():Array<huks.HuksParam>{return[{tag:huks.HuksTag.HUKS_TAG_ALGORITHM,value:huks.HuksKeyAlg.HUKS_ALG_RSA},{tag:huks.H......
  • 对抗类比赛评委计分算法
    节得分算法s1:每节比赛结束,评委二选一投票,票数和,为选手的节得分。节得分算法s2:每节比赛结束,评委二选一投票,票数多的,选手的节得分为:2分,票数少的,选手节得分为0分;两个票数一样的,各得1分。节得分算法s3:每节比赛结束,评委给2个选手打分,选手的节得分为评委得分之和。节得分算法s4:......
  • 【算法】JZ30 包含min函数的栈
    1.概述描述定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,输入操作时保证pop、top和min函数操作时,栈中一定有元素。此栈包含的方法有:push(value):将value压入栈中pop():弹出栈顶元素top():获取栈顶元素min():获取栈中最小元素......
  • 【算法】删除有序链表中的重复元素、保留重复节点的一个
    1.概述存在一个按升序排列的链表,给你这个链表的头节点head,请你删除所有重复的元素,使每个元素只出现一次。返回同样按升序排列的结果链表。本问题和【算法】删除有序链表中的重复元素、不保留重复节点很类似,但是思考起来稍微简单些,建议看完这个,看链接的这个吧。2.......