聊聊 Vue 的 Diff 算法也许没你想的那么难

1,102 阅读6分钟

前言

上篇文章咱们了解到了虚拟节点的大概思路,其实就是利用递归将一层层的dom节点用对象来表示。但是有前提,就是一开始渲染的时候可以那么写,或是第一次挂载的时候。那在更新的时候又该怎么处理呢,这时就需要用了diff算法了。

patch() 方法

  • (1). 类型不同
/**
 * 补丁方法,更新标签
 * @param {*} oldVnode 旧标签
 * @param {*} newVnode 新标签
 */
function patch(oldVnode, newVnode){
    // 如果标签不一致直接替换成新节点
    if(oldVnode.type !== newVnode.type){
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode), oldVnode.domElement);
    }
}

测试下我们写的代码:

let oldVD = createElement(
    'div',
    {id: 'testDom', data: '测试属性', key: 'testKey'},
    createElement('span', {style: {color: 'red'}}, '我是span内容'),
    '测试内容'
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
        'p',
        {},
        '我是新内容'
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒后标签内容换成了新的标签内容

  • (2). 类型相同且是文本
/**
 * 补丁方法,更新标签
 * @param {*} oldVnode 旧标签
 * @param {*} newVnode 新标签
 */
function patch(oldVnode, newVnode){
    // 如果标签不一致直接替换成新节点
    if(oldVnode.type !== newVnode.type){
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode), oldVnode.domElement);
    }
    // 类型相同 ==> 文本
    if(oldVnode.text){
        if(oldVnode.text === newVnode.text) return;
        return oldVnode.domElement.textContent = newVnode.text;
    }
}
  • (3). 类型相同且是标签
/**
 * 补丁方法,更新标签
 * @param {*} oldVnode 旧标签
 * @param {*} newVnode 新标签
 */
function patch(oldVnode, newVnode){
    // 如果标签不一致直接替换成新节点
    if(oldVnode.type !== newVnode.type){
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode), oldVnode.domElement);
    }
    // 类型相同 ==> 文本
    if(oldVnode.text){
        if(oldVnode.text === newVnode.text) return;
        return oldVnode.domElement.textContent = newVnode.text;
    }
    
    // 类型相同 ==> 是标签:需要根据新节点的属性更新老节点的属性
    let domElement = newVnode.domElement = oldVnode.domElement;
    // 根据最新的虚拟节点更新老节点的属性
    updateProperties(newVnode, oldVnode.props)
}

测试下我们写的代码:

let oldVD = createElement(
    'div',
    {id: 'testDom', style:{color: 'blue'}},
    '测试内容'
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
        'div',
        {id: 'testDom', style:{color: 'yellow'}},
        '测试内容'
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒后内容由蓝色变成了黄色

上述测试只是新旧节点的属性不同,由新节点的属性更新就节点的属性即可。但还有复杂的子节点的比对。这时又分为三种情况:

  • (3.1). 类型相同且是标签 -- 老的有子节点,新的没有
/**
 * 补丁方法,更新标签
 * @param {*} oldVnode 旧标签
 * @param {*} newVnode 新标签
 */
function patch(oldVnode, newVnode){
    // 如果标签不一致直接替换成新节点
    if(oldVnode.type !== newVnode.type){
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode), oldVnode.domElement);
    }
    // 类型相同 ==> 文本
    if(oldVnode.text){
        if(oldVnode.text === newVnode.text) return;
        return oldVnode.domElement.textContent = newVnode.text;
    }
    
    // 类型相同 ==> 是标签:需要根据新节点的属性更新老节点的属性
    let domElement = newVnode.domElement = oldVnode.domElement;
    // 根据最新的虚拟节点更新老节点的属性
    updateProperties(newVnode, oldVnode.props);
    
    let oldChildren = oldVnode.children; // 老子标签
    let newChildren = newVnode.children; // 新子标签

    // 3.1. 老的有子节点,新的没有
    if(oldChildren.length > 0 && newChildren.length == 0){
        domElement.innerHTML = ''
    }
}

测试我们写的代码

let oldVD = createElement(
    'div',
    {id: 'testDom', style:{color: 'blue'}},
    '测试内容'
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
        'div',
        {id: 'testDom', style:{color: 'yellow'}}
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒钟后内容被清空了

  • (3.2). 类型相同且是标签 -- 老的没子节点,新的有即新增
/**
 * 补丁方法,更新标签
 * @param {*} oldVnode 旧标签
 * @param {*} newVnode 新标签
 */
function patch(oldVnode, newVnode){
    // 如果标签不一致直接替换成新节点
    if(oldVnode.type !== newVnode.type){
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode), oldVnode.domElement);
    }
    // 类型相同 ==> 文本
    if(oldVnode.text){
        if(oldVnode.text === newVnode.text) return;
        return oldVnode.domElement.textContent = newVnode.text;
    }
    
    // 类型相同 ==> 是标签:需要根据新节点的属性更新老节点的属性
    let domElement = newVnode.domElement = oldVnode.domElement;
    // 根据最新的虚拟节点更新老节点的属性
    updateProperties(newVnode, oldVnode.props);
    
    let oldChildren = oldVnode.children; // 老子标签
    let newChildren = newVnode.children; // 新子标签

    // 3.1. 老的有子节点,新的没有
    if(oldChildren.length > 0 && newChildren.length == 0){
        domElement.innerHTML = ''
    }
     // 3.2. 老的没子节点,新的有即新增
    else if(newChildren.length > 0 && oldChildren.length == 0){
        for(let i = 0; i < newChildren.length; i++){
      domElement.appendChild(createDomElementFromVnode(newChildren[i]))
        }
    }
}

测试下我们写的代码


let oldVD = createElement(
    'div',
    {id: 'testDom', style:{color: 'blue'}}
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
        'div',
        {id: 'testDom', style:{color: 'yellow'}},
        '测试内容'
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒钟后内容被渲染上去了

  • (3.3). 类型相同且是标签 -- 老的有子节点,新的也有 ==> diff

这第三种比较复杂,就是我们常说的diff算法了,

/**
 * 补丁方法,更新标签
 * @param {*} oldVnode 旧标签
 * @param {*} newVnode 新标签
 */
function patch(oldVnode, newVnode){
    // 如果标签不一致直接替换成新节点
    if(oldVnode.type !== newVnode.type){
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFromVnode(newVnode), oldVnode.domElement);
    }
    // 类型相同 ==> 文本
    if(oldVnode.text){
        if(oldVnode.text === newVnode.text) return;
        return oldVnode.domElement.textContent = newVnode.text;
    }
    
    // 类型相同 ==> 是标签:需要根据新节点的属性更新老节点的属性
    let domElement = newVnode.domElement = oldVnode.domElement;
    // 根据最新的虚拟节点更新老节点的属性
    updateProperties(newVnode, oldVnode.props);
    
    let oldChildren = oldVnode.children; // 老子标签
    let newChildren = newVnode.children; // 新子标签

    // 3.1. 老的有子节点,新的没有
    if(oldChildren.length > 0 && newChildren.length == 0){
        domElement.innerHTML = ''
    }
     // 3.2. 老的没子节点,新的有即新增
    else if(newChildren.length > 0 && oldChildren.length == 0){
        for(let i = 0; i < newChildren.length; i++){
      domElement.appendChild(createDomElementFromVnode(newChildren[i]))
        }
    }
    // 3.3. 老的有子节点,新的也有 ==> diff
    else if(newChildren.length > 0 && oldChildren.length > 0){
        updateChildren(domElement, oldChildren, newChildren);
    }
}

updateChildren() 方法 : diff 算法

为了方便我们观察,我们采用列表的形式,如大家所了解的那样:diff算法是双指针比对法,即头部指针和尾部指针。所以我们先现取出新旧节点的头尾两个指针,如下:

/**
 * diff 算法
 * @param {*} parent 父节点
 * @param {*} oldChildren 旧的子节点
 * @param {*} newChildren 新的子节点
 */
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 = newdChildren[0];
    let newEndIndex = newChildren.length - 1;
    let newEndVnode = newChildren[newEndIndex];
}

具体的diff又分为几种情况,如下:

1.顺序没变的情况:头部一样,尾部新增/更新属性

/**
 * diff 算法
 * @param {*} parent 父节点
 * @param {*} oldChildren 旧的子节点
 * @param {*} newChildren 新的子节点
 */
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];
        }
    }
    // 循环结束时剩余节点则插入到父节点上
    if(newStartIndex <= newEndIndex){
        for(let i = newStartIndex; i <= newEndIndex; i++){
            parent.appendChild(createDomElementFromVnode(newChildren[i]))
        }
    }
}

// 判断是不是同一个节点
function isSameVnode(oldVnode, newVnode){
    return oldVnode.key === newVnode.key && oldVnode.type === newVnode.type;
}

测试我们的代码:

let oldVD = createElement(
    'ul', {},
    createElement('li', {style: {color: 'red'}, key: 'A'}, 'A'),
    createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B'),
    createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C'),
    createElement('li', {style: {color: 'green'}, key: 'D'}, 'D')
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
       'ul', {},
        createElement('li', {style: {color: 'red'}, key: 'A'}, 'A1'),
        createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B1'),
        createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C1'),
        createElement('li', {style: {color: 'green'}, key: 'D'}, 'D1'),
        createElement('li', {style: {color: 'pink'}, key: 'E'}, 'E1')
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

两秒钟后可以看到节点更新了

2.顺序没变的情况:尾部一样,头部新增/更新属性

/**
 * diff 算法
 * @param {*} parent 父节点
 * @param {*} oldChildren 旧的子节点
 * @param {*} newChildren 新的子节点
 */
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 beforeElement = newChildren[newStartIndex + 1] == null ?null :newChildren[newStartIndex + 1] .domElement;
           parent.insertBefore( createDomElementFromVnode(newChildren[i]),beforeElement);
        }
    }
}

测试下我们的代码:

let oldVD = createElement(
    'ul', {},
    createElement('li', {style: {color: 'red'}, key: 'A'}, 'A'),
    createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B'),
    createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C'),
    createElement('li', {style: {color: 'green'}, key: 'D'}, 'D')
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
       'ul', {},
        createElement('li', {style: {color: 'pink'}, key: 'E'}, 'E1'),
        createElement('li', {style: {color: 'red'}, key: 'A'}, 'A1'),
        createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B1'),
        createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C1'),
        createElement('li', {style: {color: 'green'}, key: 'D'}, 'D1'),
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒钟后在前面追加内容

3.旧节点的头和新节点的尾相同 或 旧节点的尾和新节点的头相同

/**
 * diff 算法
 * @param {*} parent 父节点
 * @param {*} oldChildren 旧的子节点
 * @param {*} newChildren 新的子节点
 */
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];
        }
        // 如果旧节点的头和新节点的尾相同
        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, oldEndVnode);
            // 将头部的移动尾部
            parent.insertBefore(oldEndVnode.domElement, oldStartVnode.domElement);
            // 旧节点的头部指针向后移动一个
            oldEndtVnode = oldChildren[--oldEndIndex];
            // 新节点的尾部指针向前移动一个
            newStartVnode = newChildren[++newStartIndex];
        }
    }
    // 循环结束时剩余节点则插入到父节点上
    if(newStartIndex <= newEndIndex){
        for(let i = newStartIndex; i <= newEndIndex; i++){
           let beforeElement = newChildren[newStartIndex + 1] == null ?null :newChildren[newStartIndex + 1] .domElement;
           parent.insertBefore( createDomElementFromVnode(newChildren[i]),beforeElement);
        }
    }
}

测试下我们的代码

let oldVD = createElement(
    'ul', {},
    createElement('li', {style: {color: 'red'}, key: 'A'}, 'A'),
    createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B'),
    createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C'),
    createElement('li', {style: {color: 'green'}, key: 'D'}, 'D')
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
       'ul', {},
        createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B'),
        createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C'),
        createElement('li', {style: {color: 'green'}, key: 'D'}, 'D'),
        createElement('li', {style: {color: 'red'}, key: 'A'}, 'A')
    )
    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒钟后重新渲染了

4.前后都不相同,只能暴力比对

暴力比对也可能会有复用的节点,所以为了方便比对取值,我们创建一个方法将旧节点对一个映射表,方法如下:

function createMapByKeyToIndex(oldChildren){
    let map = {};
    for(let i= 0; i < oldChildren.length; i++){
        let current = oldChildren[i];
        if(current.key)
            map[current.key] = i;
    }
    return map;
}

同样也需要在`updateChildren`方法中将旧节点转成映射表

function updateChildren(parent,oldChildren,newChildren){
    let oldStartIndex = 0;
    let oldStartVnode = oldChildren[0];
    let oldEndIndex = oldChildren.length - 1;
    let oldEndVnode = oldChildren[oldEndIndex];
    // 将旧节点转成映射表
    let map = createMapByKeyToIndex(oldChildren);
    
    ....
}

整体代码如下:

/**
 * diff 算法
 * @param {*} parent 父节点
 * @param {*} oldChildren 旧的子节点
 * @param {*} newChildren 新的子节点
 */
function updateChildren(parent,oldChildren,newChildren){
    let oldStartIndex = 0;
    let oldStartVnode = oldChildren[0];
    let oldEndIndex = oldChildren.length - 1;
    let oldEndVnode = oldChildren[oldEndIndex];
    let map = createMapByKeyToIndex(oldChildren);

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

    // 循环时不管新旧节点哪个先结束都会停止循环
    while(oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex){
        // 因为在暴力比对时有可能会被置为 undefined 所以要判断下
        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, oldEndVnode);
                // 将头部的移动尾部
                parent.insertBefore(oldEndVnode.domElement, oldStartVnode.domElement);
                // 旧节点的头部指针向后移动一个
                oldEndVnode = oldChildren[--oldEndIndex];
                // 新节点的尾部指针向前移动一个
                newStartVnode = newChildren[++newStartIndex];
            }
            // 都不相同,暴力比对
            else{
                let index = map[newStartVnode.key];
                // 新节点中没有此项
                if(index == null){
                    parent.insertBefore(createDomElementFromVnode(newStartVnode), oldStartVnode.domElement)
                }
                // 若有此项,则复用
                else{
                    let toMoveVnode = oldChildren[index];
                    patch(toMoveVnode, newStartVnode);
                    // 将符合项移动到前头复用
                    parent.insertBefore(toMoveVnode.domElement, oldStartVnode.domElement);
                    // 并将移动项所在位置变为空
                    oldChildren[index] = undefined;
                }
                newStartVnode = newChildren[++newStartIndex];
            }
        }
    }
    // 循环结束时剩余节点则插入到父节点上
    if(newStartIndex <= newEndIndex){
        for(let i = newStartIndex; i <= newEndIndex; i++){
           let beforeElement = newChildren[newStartIndex + 1] == null ?null :newChildren[newStartIndex + 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);
        }
    }
}

测试下我们的代码:

let oldVD = createElement(
    'ul', {},
    createElement('li', {style: {color: 'red'}, key: 'A'}, 'A'),
    createElement('li', {style: {color: 'blue'}, key: 'B'}, 'B'),
    createElement('li', {style: {color: 'yellow'}, key: 'C'}, 'C'),
    createElement('li', {style: {color: 'green'}, key: 'D'}, 'D')
)

window.onload = function(){
    let div = document.getElementById('test_container');
    render(oldVD, div)

    let newVD = createElement(
       'ul', {},
        createElement('li', {style: {color: 'blue'}, key: 'G'}, 'G'),
        createElement('li', {style: {color: 'yellow'}, key: 'E'}, 'E'),
        createElement('li', {style: {color: 'green'}, key: 'D'}, 'D'),
        createElement('li', {style: {color: 'red'}, key: 'A'}, 'A'),
        createElement('li', {style: {color: 'red'}, key: 'F'}, 'F'),
        createElement('li', {style: {color: 'red'}, key: 'M'}, 'M')
    )

    setTimeout(() => {
        patch(oldVD, newVD)
    }, 2000);
}

可以看到两秒钟后重新渲染了

到此虚拟节点以及Diff算法的一些大概逻辑咱们就说完了,希望对你有所帮助
欢迎点赞收藏评论留言~~~