Vue.js设计与实现阅读笔记(九)

渲染器

11.快速Diff算法

11.1相同的前置元素和后置元素

快速Diff算法包含预处理步骤,先处理新旧两组子节点中相同的前置节点和相同的后置节点。

function patchKeyedChildren(n1, n2, container) {
    const oldChildren = n1.children
    const newChildren = n2.children
    // 处理相同的前置节点
    let j = 0
    let oldVNode = oldChildren[j]
    let newVNode = newChildren[j]
    while (oldVNode.key === newVNode.key) {
        patch(oldVNode, newVNode, container)
        j++
        oldVNode = oldChildren[j]
        newVNode = newChildren[j]
    }
    // 处理相同的后置节点
    let oldEnd = oldChildren.length - 1
    let newEnd = newChildren.length - 1
    oldVNode = oldChildren[oldEnd]
    newVNode = newChildren[newEnd]
    while (oldVNode.key === newVNode.key) {
        patch(oldVNode, newVNode, container)
        oldEnd--
        newEnd--
        oldVNode = oldChildren[oldEnd]
        newVNode = newChildren[newEnd]
    }
    // 新增节点
    if (j > oldEnd && j <= newEnd) {
        const anchorIndex = newEnd + 1
        const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null
        while (j <= newEnd) {
            patch(null, newChildren[j++], container, anchor)
        }
    } else if (j > newEnd && j <= oldEnd) {
        // 删除节点
        while (j <= oldEnd) {
            unmount(oldChildren[j++])
        }
    }
}

11.2判断是否需要进行DOM移动操作

预处理后,对于剩余的未处理节点,需要判断哪些节点需要移动。
首先,构造一个数组source,它的长度等于新的一组子节点在经过预处理之后剩余未处理节点的数量,且初始值均为-1。source将用来存储新的一组子节点中的节点在旧的一组子节点中的位置索引,之后将会使用它计算出一个最长递增子序列,并用于辅助完成DOM移动的操作。
为了快速填充source,构建一张索引表,用来存储节点的key和节点位置索引之间的映射。
为了判断节点是否需要移动,新增两个变量moved和pos。前者的初始值为false,代表是否需要移动节点,后者的初始值为0,代表遍历旧的一组子节点的过程中遇到的最大索引值k。

function patchKeyedChildren(n1, n2, container) {
    // ...
    if (j > oldEnd && j <= newEnd) {
        // ...
    } else if (j > newEnd && j <= oldEnd) {
        // ...
    } else {
        // 构造source数组
        const count = newEnd - j + 1
        const source = new Array(count).fill(-1)
        const oldStart = j
        const newStart = j
        let moved = false
        let pos = 0
        // 构造索引表
        const keyIndex = {}
        for (let i = newStart; i <= newEnd; i++) {
            keyIndex[newChildren[i].key] = i
        }
        let patched = 0
        for (let i = oldStart; i <= oldEnd; i++) {
            oldVNode = oldChildren[i]
            if (patched <= count) {
                const k = keyIndex[oldVNode.key]
                if (typeof k !== 'undefined') {
                    newVNode = newChildren[k]
                    patch(oldVNode, newVNode, container)
                    source[k - newStart] = i
                    // 判断节点是否需要移动
                    if (k < pos) {
                        moved = true
                    } else {
                        pos = k
                    }
                } else {
                    unmount(oldVNode)
                }
            } else {
                unmount(oldVNode)
            }
        }
    }
}

11.3如何移动元素

计算source数组的最长递增子序列,并返回该序列中的元素在source数组中的位置索引。最长递增子序列所指向的节点即为不需要移动的节点。

function patchKeyedChildren(n1, n2, container) {
    // ...
    if (j > oldEnd && j <= newEnd) {
        // ...
    } else if (j > newEnd && j <= oldEnd) {
        // ...
    } else {
        // ...
        if (moved) {
            // 计算最长递增子序列
            const seq = lis(source)
            let s = seq.length - 1
            let i = count - 1
            for (; i >= 0; i--) {
                if (source[i] === -1) {
                    // 该节点是新节点,需要挂载
                    const pos = i + newStart
                    const newVNode = newChildren[pos]
                    const nextPos = pos + 1
                    const anchor = nextPos < newChildren.length ? newChildren[nextPos].el : null
                    patch(null, newVNode, container, anchor)
                } else if (i !== seq[s]) {
                    // 该节点需要移动
                    const pos = i + newStart
                    const newVNode = newChildren[pos]
                    const nextPos = pos + 1
                    const anchor = nextPos < newChildren.length ? newChildren[nextPos].el : null
                    insert(newVNode.el, container, anchor)
                } else {
                    // 该节点不需要移动
                    s--
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值