vue原理----diff算法核心逻辑,带详细笔记

在这里插入图片描述

图片配合下面整理笔记一起看。

总结:

1、通过 h 函数创建虚拟节点, 调用 vnode函数 return { sel, data, children, text, elm }

2、patch 根据虚拟节点创建真正的 DOM insertBefore 插入到页面,上树之前还是 虚拟格式,那么调用 createElement创建节点,放在自身的 elm 属性上

最后通过 oldVnode.elm.parentNode.insertBefore(newDom, oldVnode.elm) 插入页面,移除旧的 oldVnode
    
    虚拟节点种 key相同,sel 选择器相同,给虚拟节点 打补丁,对比
    其中有虚拟DOM对比,调用 patchVnode(oldVnode, newVnode)

3、patchVnode 中进行虚拟节点对比

    第一种: newVnode 为 text 文本,oldVnode 有 文本 || 数组, 直接替换旧的
    第二种: newVnode 为数组,oldVnode 为 text || 数组
            2.1 如果newVnode为数组,oldVnode为text的情况,将oldVnode种的text删除,插入新创建的节点。
            2.2 如果newVnode为数组,oldVnode为数组,精细化对比
             调用  updateVnode 函数

4、updateVnode 两个虚拟节点孩子进行对比

    注意1: 以旧节点为标杆,对旧节点进行移动,删除,内容替换, 新节点一直 ++
    注意2: parentElm.insertBefore 改变的是页面中的元素,当前 newChild、oldChild不受影响
    
    while(newStartIndex <= newEndIndex && oldStartIndex <= oldEndIndex) {

4.1 第一种,新前与旧前对比,只改变旧节点种的虚拟dom

            A text:1     A text:2
            B text:2     B text:2
            C text:3     C text:2

4.2 第二种,新后与旧后对比,只改变旧节点种的虚拟dom

            D text:1     C text:2
            B text:1     B text:2
            A text:1     A text:2

4.3 第三种,新后与旧前对比,将新后匹配到的旧节点移动到旧后之后

            patchVnode(oldStartVnode, newEndVnode) // 先替换oldStartVnode种的值
            parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling) //将oldStartVnode先插入,在移动至旧后下一个节点依次向下
            旧前           新前
            A text:1     C text:2 
            B text:1     B text:2
            C text:1     A text:2
            旧后          新后
            B text:2
            A text:2

4.4 第四种,旧后与新前对比,将新前匹配到的旧后节点移动到旧前的前面

            patchVnode(oldEndVnode, newStartVnode)
            parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm) //将oldEndVnode先插入,在移动至旧前前面,依次向前

            C text:2
            B text:2
            旧前           新前
            A text:1     C text:2 
            B text:1     B text:2
            C text:1     D text:2
            旧后          新后

4.5 第五种,都不满足,将旧节点的 key 跟 下标存下来,移动新节点

            let zIndex = mapKey[newStartVnode.key]
            if (zIndex === undefined) { // 不存在,新建节点,插入到旧前的前面
                parentElm.insertBefore(creatElement(newStartVnode), oldStartVnode.elm)
            } else {
                let remove = oldChild[zIndex]
                // 存在,先替换文本,再将旧的改为 undefined,并移动旧的节点
                patchVnode(remove, newStartVnode)
                oldChild[zIndex] = undefined
                parentElm.insertBefore(remove.elm, oldStartVnode.elm) // 在parentElm中新增一个节点,移动到 oldStartVnode 前面
            }
            ++newStartIndex
    }
    while循环中发现有一方执行完了,下标不相等,跳出循环

4.6 判断新节点中有剩余的节点,是要新增的

         newStartIndex <= newEndIndex
         
            旧前           新前
            A text:1     A text:2 
            B text:1     B text:2
            C text:1     C text:2
                          D text:2
                          E text:2
            旧后          新后
       
        for (let i = newStartIndex; i <= newEndIndex; i ++) {
            parentElm.insertBefore(creatElement(newChild[i]), oldChild[oldStartIndex].elm) // 如果没有找到这个元素,就在最后插入
        }

4.7 判断旧节点中还有剩余的节点,是需要删除的节点

         oldStartIndex <= oldEndIndex

            旧前           新前
            A text:1     A text:2 
            B text:1     B text:2
            C text:1     C text:2
            D text:1
            E text:1
            旧后          新后
        
        for (let i = oldStartIndex; i <= oldEndIndex; i ++) {
            if (oldChild[i] && oldChild[i].elm !== undefined) {
                parentElm.removeChild(oldChild[i].elm)
            }
        }

vnode.js

export default function(sel, data, children, text, elm) {
    const key = data.key ? data.key : undefined
    return {
        sel,
        data,
        children,
        text,
        key,
        elm
    }
}

h.js

export default function h(sel, data, c) {
    // console.log(sel, data, c, '--->>')
    if (arguments.length !== 3) {
        throw new Error('请输入三位有效数字')
    }
    if ((typeof c) === 'string') {
        return vnode(sel, data, undefined, c, undefined)
    }
    if (Array.isArray(c)) {
        const arr = []
        for (let i = 0; i < c.length; i ++) {
            if (!(Object.prototype.toString.call(c[i]) === '[object Object]' && c[i].hasOwnProperty('sel'))) {
                throw new Error('输入的数组格式有误!!!')
                return
            }
            arr.push(c[i])
            // console.log('24--->', vnode(sel, data, arr, undefined, undefined))
        }
        console.log(arr, 'hhhhh--141414------->>>')
        return vnode(sel, data, arr, undefined, undefined)
    }
    if (Object.prototype.toString.call(c) === '[object Object]' && c.hasOwnProperty('sel')) {
        return vnode(sel, data, [c], undefined, undefined)
    }
    throw new Error('输入的格式有误~~')

}

patch.js

import vnode from './vnode'
import creatElement from './creatElement'
import patchVnode from './patchVnode'
export default function patch(oldVnode, newVnode) {
    console.log(oldVnode, newVnode)
    if (!oldVnode.sel) {
        const text = oldVnode.innerText ? oldVnode.innerText : undefined
        oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], text, oldVnode)
    }
    if (oldVnode.sel === newVnode.sel && oldVnode.key !== undefined && newVnode.key !== undefined && oldVnode.key === newVnode.key) {
        patchVnode(oldVnode, newVnode)
        return
    }
    // 创建新的节点,暴力删除旧的
    const newDom = creatElement(newVnode)
    console.log(newDom, 'newDom')
    console.log(oldVnode, 'oldVnode')
    // debugger
    if (newDom) {
         // insertBefore('插入的新节点的elm对象', '标杆,要插入的位置') 方法在您指定的已有子节点之前插入新的子节点。
        oldVnode.elm.parentNode.insertBefore(newDom, oldVnode.elm)
    }
    oldVnode.elm.remove(oldVnode.elm)
}

patchVnode.js

import updateVnode from './updateVnode'
import creatElement from './creatElement'
/**
 * @description: 替换旧节点中的innerText, 如果旧节点中的 text为 123, 新节点的 text为 234, 此时就进行替换,如果为数组直接appendChild
 * @param {Object} oldVnode 旧的虚拟节点
 * @param {Object} newVnode 新的虚拟节点
 * @return {*}
 */
export default function patchVnode(oldVnode, newVnode) {
    
    // console.log('oldVnode', oldVnode, 'newVnode', newVnode)
    // 第一种: newVnode 为 text 文本,oldVnode 有 文本 || 数组, 直接替换旧的
    if (newVnode.text && newVnode.text !== undefined) {
        if (oldVnode.text && oldVnode.text !== undefined && newVnode.text === oldVnode.text) {
            return
        }
        oldVnode.elm.innerText = newVnode.text
    } else { //第二种: newVnode 为数组,oldVnode 为 text || 数组
        console.log('zzzzzzz');
            // 2.1 如果newVnode为数组,oldVnode为text的情况,将oldVnode种的text删除,插入新创建的节点。
        if ((oldVnode.text || oldVnode.text === undefined) && (oldVnode.children === undefined || oldVnode.children.length === 0)) {
            oldVnode.elm.innerHTML = ''
            for (let i = 0; i < newVnode.children.length; i ++) {
                const itemDom = creatElement(newVnode.children[i])
                oldVnode.elm.appendChild(itemDom)
            }
            // 2.2 如果newVnode为数组,oldVnode为数组,精细化对比
        } else if (oldVnode.children && oldVnode.children.length > 0 && newVnode.children && newVnode.children.length > 0) {
            console.log('进行最精细化比较oldVnode。elm',oldVnode.elm, oldVnode, newVnode)
            updateVnode(oldVnode.elm, oldVnode.children, newVnode.children)
        }
    }
}

creatElement.js

export default function creatElement(newVnode) { // 创建元素节点
    // 创建元素节点
    const container = document.createElement(newVnode.sel)
    // console.log(container, '123445--->>')
    // 新的虚拟dom中是否为文本节点,如果是,将文本插入到创建的节点中
    if (newVnode.text && (newVnode.children === undefined || newVnode.children.length === 0)) {
        container.innerText = newVnode.text
        // 如果为数组节点,遍历输入,插入到创建的节点中
    } else if (newVnode.children && newVnode.children.length > 0) {
        for (let i = 0; i < newVnode.children.length;i ++) {
            let liDom = creatElement(newVnode.children[i])
            container.appendChild(liDom)
        }
    }
    newVnode.elm = container
    console.log('creatElement------>>>>newVnode', newVnode)
    // 返回dom
    return newVnode.elm
}

updateVnode.js

import patchVnode from "./patchVnode"
import creatElement from "./creatElement"
function checkoutSameVnode(oldVnode, newVnode) {
    return oldVnode.sel === newVnode.sel && oldVnode.key === newVnode.key
}
/**
 * @description: 最小量更新
 * @param {Object} parentElm oldVnode.elm(旧虚拟节点的elm元素对象)
 * @param {Array} oldChild 旧的节点中的数组
 * @param {Array} newChild 新节点中的数组
 */
export default function updateVnode(parentElm, oldChild, newChild) {
    /*
        oldChild
        newChild
        页面中获取数据都是以这两个数组为源头,最终改的却是树中的值
        也就是说,根据虚拟dom进行细小量更新,最后直接更新的是旧的DOM中的值 例如: oldVnode.elm.innerText = newVnode.text
    */
    debugger
    // 新前
    let newStartIndex = 0
    // 旧前
    let oldStartIndex = 0
    // 新后
    let newEndIndex = newChild.length - 1
    // 旧后
    let oldEndIndex = oldChild.length - 1
    // 新前的虚拟节点
    let newStartVnode = newChild[0]
    // 新后的虚拟节点
    let newEndVnode = newChild[newEndIndex]
    // 旧前的虚拟节点
    let oldStartVnode = oldChild[0]
    // 旧后的虚拟节点
    let oldEndVnode = oldChild[oldEndIndex]
    let mapKey = null
    // console.log('newStartVnode:', newStartVnode);
    // console.log('newEndVnode:', newEndVnode);
    // console.log('oldStartVnode:', oldStartVnode);
    // console.log('oldEndVnode:', oldEndVnode);
    while(newStartIndex <= newEndIndex && oldStartIndex <= oldEndIndex) {
        console.log('我进入了死循环----》》》')
        if (oldStartVnode == null || oldStartVnode === undefined) {
            oldStartVnode = oldChild[++oldStartIndex]; // Vnode might have been moved left
        }
        else if (oldEndVnode == null || oldEndVnode === undefined) {
            oldEndVnode = oldChild[--oldEndIndex];
        }
        else if (newStartVnode == null || newStartVnode === undefined) {
            newStartVnode = newChild[++newStartIndex];
        }
        else if (newEndVnode == null || newEndVnode === undefined) {
            newEndVnode = newChild[--newEndIndex];
        }
        // 1,先写4个命中
        else if (checkoutSameVnode(oldStartVnode, newStartVnode)) {
            console.log('第一种,新前与旧前对比,只改变虚拟dom')
            patchVnode(oldStartVnode, newStartVnode)
            newStartVnode = newChild[++newStartIndex]
            oldStartVnode = oldChild[++oldStartIndex]
        } else if (checkoutSameVnode(oldEndVnode, newEndVnode)) {
            console.log('第二种,新后与旧后对比,只改变虚拟dom', oldEndVnode, newEndVnode, oldEndIndex, oldStartIndex)
            patchVnode(oldEndVnode, newEndVnode)
            oldEndVnode = oldChild[--oldEndIndex]
            newEndVnode = newChild[--newEndIndex]
            console.log('第二种,新后与旧后对比,只改变虚拟dom',oldEndVnode, newEndVnode, oldEndIndex, oldStartIndex)
        } else if (checkoutSameVnode(oldStartVnode, newEndVnode)) {
             /*   
                const vNode1 = h('ul', {}, [
                    h('li', { key: 'A' }, 'A'),
                    h('li', { key: 'B' }, 'B'),
                    h('li', { key: 'C' }, 'C')
                ])
                
                const vNode2 = h('ul', {}, [
                    h('li', { key: 'F' }, 'F'),
                    h('li', { key: 'E' }, 'E'),
                    h('li', { key: 'D' }, 'D'),
                    h('li', { key: 'C' }, 'C1'),
                    h('li', { key: 'B' }, 'B'),
                    h('li', { key: 'A' }, 'A'),
                ])
               */
            //   旧节点不变, 就是修改旧节点中的内容,然后移动旧前第一个节点节点到 所有旧后没处理节点的最后面插入
            // 如何移动节点???只要你插入一个已经在DOm树上的节点,它就会被移动。
            // insertBefore('插入的新节点', '标杆,如果为null插入到parentElm最后边')
            console.log('新后与旧前命中-- 三三三 -->>>此时要移动节点,移动新前指向的这个节点到旧节点的旧后面', oldStartVnode, newEndVnode,oldEndVnode, oldEndVnode.elm.nextSibling)
            patchVnode(oldStartVnode, newEndVnode)
            // nextSibling 属性返回指定节点之后紧跟的节点,在相同的树层级中。
            // 第一次对比,A 跟 A 对比,但是 vNode1中的 elm === c, c.nextSibling后紧跟的节点 为null,所以在最后插入一个A 节点
            // 第二次对比,B 跟 B 对比,但是 vNode1中的 elm === c, c.nextSibling后紧跟的节点 为 A,所以以 A 为标杆,移动 vNode1 中 B 到 A 之前。
            // 将新后匹配到的旧节点移动到旧后之后
            parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
            newEndVnode = newChild[--newEndIndex]
            oldStartVnode = oldChild[++oldStartIndex]
        } else if (checkoutSameVnode(oldEndVnode, newStartVnode)) {
            // 如何移动节点???只要你插入一个已经在DOm树上的节点,它就会被移动。
          /*   const vNode1 = h('ul', {}, [
                h('li', { key: 'A' }, 'A'),
                h('li', { key: 'B' }, 'B'),
                h('li', { key: 'C' }, 'C'),
                h('li', { key: 'D' }, 'D'),
                h('li', { key: 'E' }, 'E'),
              ])
              
              const vNode2 = h('ul', {}, [
                h('li', { key: 'E' }, 'E'),
                h('li', { key: 'C' }, 'C1'),
                h('li', { key: 'M' }, 'M1'),
              ]) */
            //  将新前匹配到的旧节点移动到旧前的前面
            console.log('旧后与新前==四四四===》此时要移动节点,移动新前指向的这个节点到旧节点的旧前')
            patchVnode(oldEndVnode, newStartVnode)
            parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
            oldEndVnode = oldChild[--oldEndIndex]
            newStartVnode = newChild[++newStartIndex]
        } else {

            // 第四步---- 4个命中都执行完了,新 旧开始对比,如果旧节点中存在新节点,替换, 如果为undefined,那么是要新增的。
            console.log('newStartIndex', newStartIndex,)
            // console.log('4种命中都走完了, 还有剩下的怎么操作,暂时先不行')
            if (!mapKey) {
                mapKey = {}
                for (let i = oldStartIndex ; i <= oldEndIndex; i ++) {
                    let key = oldChild[i].key
                    if (key !== undefined) {
                        mapKey[key] = i
                    }
                }
            }
            let zIndex = mapKey[newStartVnode.key]
            // 如果不存在,插入
            if (zIndex === undefined) {
                parentElm.insertBefore(creatElement(newStartVnode), oldStartVnode.elm)
            } else {
                // 那么进行判断时就需要使用 新节点种的 key, 如果新节点种的key不在 mapKey 当中,就为新增的节点,那么就是需要移动的
                /* 
                     const oldvNode5 = h('ul', {key: 'ul'}, [
                        h('li', { key: 'B' }, 'B'),
                        h('li', { key: 'A' }, 'A'),
                        h('li', { key: 'C' }, 'C')
                    ])

                    const newvNode5 = h('ul', {key: 'ul'}, [
                        h('li', { key: 'A' }, 'A12345'),
                        h('li', { key: 'C' }, 'C1C!C!'),
                        h('li', { key: 'F' }, 'FFFF'),
                    ])
                    patch(container, oldvNode5)
                    btn.onclick = () => {
                        patch(oldvNode5, newvNode5)
                    }

                    let arr = [{a: 1}, {b: 2}]
                    let remove = arr[0]
                    remove.a = 3
                    arr[0] = undefined
                    arr.unshift(remove)
                    console.log(arr, '这里不会收到影响吧')
                */
                let remove = oldChild[zIndex]
                // 存在,先替换文本,再将旧的改为 undefined,并移动旧的节点
                patchVnode(remove, newStartVnode)
                oldChild[zIndex] = undefined
                parentElm.insertBefore(remove.elm, oldStartVnode.elm)
            }
            newStartVnode = newChild[++newStartIndex]
        }
    }
   
    // 如果循环完以后,新新节点还有剩余区间节点,说明全部是要新增的
    // 先在while中查找,直到定位到B节点,跳出循环,进入此循环,
    if (newStartIndex <= newEndIndex) {
        /*
            const oldvNode7 = h('ul', {key: 'ul'}, [
                h('li', { key: 'B' }, 'B1111'),
            ])
            
            const newvNode7 = h('ul', {key: 'ul'}, [
                h('li', { key: 'D' }, 'D'),
                h('li', { key: 'B' }, 'B2222'),
                h('li', { key: 'G' }, 'G'),
                h('li', { key: 'F' }, 'FFF'),
            ])
            patch(container, oldvNode7)
            btn.onclick = () => {
                patch(oldvNode7, newvNode7)
            }
        */
        

        // 第一种:不能完全跟着源码走,因为我们的 newChild 中的 i 项都是未上树的,没有elm 属性
        // let before = newChild[newEndIndex + 1] === null ? null : newChild[newEndIndex + 1].elm
        // parentElm.insertBefore(creatElement(newChild[i]), before)
        // 第二种:
        // parentElm.insertBefore(creatElement(newChild[i]), oldChild[oldStartIndex].elm)
        // 第三种:
        // let before = oldChild[oldStartIndex].elm ? oldChild[oldStartIndex].elm : null
        // parentElm.insertBefore(creatElement(newChild[i]), before)
        for (let i = newStartIndex; i <= newEndIndex; i ++) {
            parentElm.insertBefore(creatElement(newChild[i]), oldChild[oldStartIndex].elm)
            // parentElm.insertBefore(creatElement(newChild[i]), before)
            console.log('parentElm--->', parentElm)
        }
    // 第三: 如果新的 oldStartIndex <=  oldEndIndex 说明有被删除的节点
    // 先在while中查找,直到定位到B节点,跳出循环,进入此循环,
    } else if (oldStartIndex <= oldEndIndex) {
        for (let i = oldStartIndex; i <= oldEndIndex; i ++) {
            if (oldChild[i] && oldChild[i].elm !== undefined) {
                parentElm.removeChild(oldChild[i].elm)
            }
        }
    }
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值