Vue2、Vue3中diff算法剖析

虚拟DOM和key

Vue为什么需要虚拟DOM?

虚拟 DOM 在 Vue 中起到了优化性能提供跨平台兼容性 以及简化开发流程的作⽤。

  • 虚拟 DOM 可以减少直接操作实际 DOM 的次数。
  • 虚拟 DOM 是⼀个抽象层,将实际 DOM 抽象为⼀个跨平台 的表示形式。使得vue 可以在不同的平台上运⾏。 Vue 会通过⽐较新旧虚拟
  • DOM 树的差异(Diff算法),找出需要更新的部分进⾏更新。

Vue中key的作⽤?

对节点进⾏标识(相同),⽤于优化节点更新。key在同⼀层级的兄弟节点中必须是唯⼀的

  • 在节点复⽤时,判断是否是相同节点。主要看标签和key是否相同,如果相同则可以进⾏复⽤

Vue2中Diff算法解析

Vue 2 的 diff 算法通过递归、双指针和优化策略来实现的,是同层级比较,不会涉及到跨级比对

  • 同层级节点的⽐较 (⽐较节点的标签、Key 和属性)
  • ⽐较⼦节点 (采⽤双指针⽅式进⾏⽐较),递归⽐较⼦节点

实现Vue2Diff算法

创建虚拟节点

虚拟节点就是一个对象来描述真实节点

h.js

// h.js

// 创建元素节点
export function createElement(tag, data = {
    },...children) {
   
    let key = data.key; // key属性
    if (key) {
   
        delete data.key
    }
    return vnode(tag,data,key,children)
}
// 创建文本节点
export function createTextNode(text) {
   
    return vnode(undefined,undefined,undefined,undefined,text)
}

function vnode(tag,data,key,children,text) {
   
    return {
    // -> vnode.key  // vnode.data.key 不存在
        tag,data,key,children,text
    }
}

生成真实节点

完整patch.js

export function patch(oldVnode, vnode) {
   
    // 判断oldVnode是一个元素节点?
    if (oldVnode.nodeType) {
    // 元素
        const el = createElm(vnode);
        oldVnode.appendChild(el);
    } else {
   
        patchVnode(oldVnode, vnode); // 从根开始比较的
    }
}
function isSameVnode(oldVnode, vnode) {
    // 必须标签一样key 一样才是同一个元素
    return (oldVnode.tag === vnode.tag) && (oldVnode.key === vnode.key)
}
function patchVnode(oldVnode, vnode) {
   
    // 比较两个节点 (节点需要能复用)
    if (!isSameVnode(oldVnode, vnode)) {
   
        // 如果不是相同节点,将老dom元素直接替换成新元素即可
        return oldVnode.el.parentNode.replaceChild(createElm(vnode), oldVnode.el)
    }
    // 走到这里说明之前和现在的节点是同一个节点, 要复用节点
    const el = vnode.el = oldVnode.el
    if (!oldVnode.tag) {
    // 文本比较文本内容,有变化复用文本节点更新内容
        if (oldVnode.text !== vnode.text) {
   
            el.textContent = vnode.text
        }
    }
    // 除了文本那就是元素了, 元素的话需要比较自己的属性和儿子
    updateProperties(vnode, oldVnode.data); // 更新属性,需要和老的比对
    // 比较双方儿子
    let oldChildren = oldVnode.children || [];
    let newChildren = vnode.children || [];
    // 双方都有儿子
    if (oldChildren.length > 0 && newChildren.length > 0) {
   
        // 比较双方的儿子
        updateChildren(el, oldChildren, newChildren); // 交给此方法来更新
    } else if (oldChildren.length > 0) {
   
        el.innerHTML = '';
    } else if (newChildren.length > 0) {
   
        for (let i = 0; i < newChildren.length; i++) {
   
            el.appendChild(createElm(newChildren[i]))
        }
    }
    // 之前有儿子 现在没儿子 把以前的儿子删除掉
    // 之前的没儿子 现在有儿子 直接将现在的儿子插入即可
}
// 给dom元素添加样式
function updateProperties(vnode, oldProps = {
    }) {
   
    const newProps = vnode.data || {
   }
    const el = vnode.el;
    // 对于属性来说新的要直接生效 但是老的里面有的新的没有还要移除
    let newStyle = newProps.style || {
   }
    let oldStyle = oldProps.style || {
   };
    for (let key in oldStyle) {
    // 老的样式有,新的没有要删除dom元素的样式
        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 {
   
            el.setAttribute(key, newProps[key])
        }
    }
}
// 递归创建节点
function createElm(vnode) {
   
    let {
    tag, children, text } = vnode
    // 如果标签名是字符串说明是一个元素节点
    if (typeof tag === 'string') {
   
        // createElement DOMapi
        vnode.el = document.createElement(tag);
        updateProperties(vnode)
        children.forEach(child => vnode.el.appendChild(createElm(child)))
    } else {
   
        vnode.el = document.createTextNode(text)
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值