snabbdom源码解析

 

 

function patch (oldVnode, newVnode) {
    // 判断oldVnode 是不是虚拟节点
    if (oldVnode.sel == "" || oldVnode.sel == undefined) {
        oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, undefined, undefined, oldVnode)
    }
    // 判断oldVnode和newVnode是不是同一节点
    if (sameVnode(oldVnode, newVnode)) {
        console.log("新旧节点是同一个,进行精细化比较")
        patchVnode(oldVnode, newVnode)
    } else {
        console.log("暴力删除旧节点,插入新节点")
        let newDom = createElement(newVnode)
        oldVnode.elm.parentNode.insertBefore(newDom, oldVnode.elm)
        // 删除旧节点
        oldVnode.elm.parentNode.removeChild(oldVnode.elm)
    }
}

function sameVnode(oldVnode, newVnode) {
    return oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key
}

function patchVnode(oldVnode, newVnode) {
    if (oldVnode === newVnode) return

    if (newVnode.text != "" && (newVnode.children == undefined || newVnode.children.length == 0)) {
        // 新节点有text 
        oldVnode.elm.innerText = newVnode.text
    } else {
        // 新节点有children
        if (Array.isArray(oldVnode.children) && oldVnode.children.length) {
            // 新节点和老节点都有children
            console.log("新节点和老节点都有children")
            updateChildren(oldVnode.elm, oldVnode.children, newVnode.children)
        } else {
            // 新节点有children 老节点为文字
            oldVnode.elm.innerHTML = ""
            for (let i = 0; i < newVnode.children.length; i++) {
                let chDom = createElement(newVnode.children[i])
                oldVnode.elm.appendChild(chDom)
            }
        }
    }
}

1. diff算法中,新旧节点都有子节点的情况。

    先声明四个指针,和四个指针对应的vnode。然后进行命中查找,命中代码如下,分四种情况。

  • 新前和旧前

  • 新后和旧后

  • 新后和旧前

  • 新前和旧后

命中其中一种情况就不往下查找,改变指针和对应的虚拟节点,特别要说明的是如果新后和旧前命中,需要将命中的节点移动到旧后的后面。如果新前和旧后命中,那么需要将命中的节点移动到旧前的前面。如果四种情况都没有命中的话,那么需要将当前节点去旧节点中去寻找,如果找到,移动节点。如果旧节点中不存在,那么需要创建节点并插入到旧前的前面。

function updateChildren(parentElm, oldCh, newCh) {
    // 四个标记
    let newStartIdx = 0
    let oldStartIdx = 0

    let newEndIdx = newCh.length - 1
    let oldEndIdx = oldCh.length - 1

    // 四个标记对应的vnode
    let newStartVnode = newCh[newStartIdx]
    let oldStartVnode = oldCh[oldStartIdx]

    let newEndVnode = newCh[newEndIdx]
    let oldEndVnode = oldCh[oldEndIdx]

    let keyMap;

    // console.log(newStartVnode, oldStartVnode)
    // console.log(newEndVnode, oldEndVnode)

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        console.log("☆")
        // while语句最开始应该略过undefined,再判断四种情况
        if (newStartVnode == null || newStartVnode == undefined) {
            newStartVnode = newCh[++newStartIdx]

        } else if (newEndVnode == null || newEndVnode == undefined) {
            newEndVnode = newCh[--newEndIdx]

        } else if (oldStartVnode == null || oldStartVnode == undefined) {
            oldStartVnode = oldCh[++oldStartIdx]

        } else if (oldEndVnode == null || oldEndVnode == undefined) {
            oldEndVnode = oldCh[--oldEndIdx]

        } else if (sameVnode(newStartVnode, oldStartVnode)) {
            console.log("1新前和旧前")
            patchVnode(oldStartVnode, newStartVnode)
            newStartVnode = newCh[++newStartIdx]
            oldStartVnode = oldCh[++oldStartIdx]

        } else if (sameVnode(newEndVnode, oldEndVnode)) {
            console.log("2新后和旧后")
            patchVnode(oldEndVnode, newEndVnode)
            newEndVnode = newCh[--newEndIdx]
            oldEndVnode = oldCh[--oldEndIdx]

        } else if (sameVnode(newEndVnode, oldStartVnode)) {
            console.log("3新后和旧前")
            // 情况三命中需要移动节点到旧后的后面
            parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)

            patchVnode(oldStartVnode, newEndVnode)
            newEndVnode = newCh[--newEndIdx]
            oldStartVnode = oldCh[++oldStartIdx]

        } else if (sameVnode(newStartVnode, oldEndVnode)) {
            console.log("4新前和旧后")
            // 情况四命中需要移动节点到旧前节点的前面
            parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)

            patchVnode(oldEndVnode, newStartVnode)
            newStartVnode = newCh[++newStartIdx]
            oldEndVnode = oldCh[--oldEndIdx]

        } else {
            // 四种情况都没有命中的情况
            console.log("5四种情况都没有命中")
            // 生成一个keyMap
            if (!keyMap) {
                keyMap = {}
                for (let i = oldStartIdx; i <= oldEndIdx; i++) {
                    let key = oldCh[i].key
                    if (key) {
                        keyMap[key] = i
                    }
                }
            }
            // console.log(keyMap)
            let idxInOld = keyMap[newStartVnode.key]
            if (idxInOld == undefined) {
                // 如果新节点不在旧节点中,那么新增
                parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
            } else {
                // 如果新节点在旧节点中,那么移动
                let moveToVnode = oldCh[idxInOld]
                // 对比一下
                patchVnode(moveToVnode, newStartVnode)
                // 移动
                parentElm.insertBefore(moveToVnode.elm, oldStartVnode.elm)
                // 标记当前节点已处理
                oldCh[idxInOld] = undefined
            }
            // 改变指针
            newStartVnode = newCh[++newStartIdx]
        }
    }
    // 循环结束还有两种情况
    if (newStartIdx <= newEndIdx) {
        // 新节点比旧节点多,那么就需要将新节点中未处理的节点添加到旧节点中
        console.log('新节点比旧节点多')
        for (let i = newStartIdx; i <= newEndIdx; i++) {
            parentElm.insertBefore(createElement(newCh[i]), oldStartVnode.elm)
        }
    } else if (oldStartIdx <= oldEndIdx) {
        // 旧节点比新节点多, 那么需要删除旧节点
        console.log("旧节点比新节点多")
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
            if (oldCh[i]) {
                parentElm.removeChild(oldCh[i].elm)
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值