使用diff算法对比两个数据,获取两个数据的属性差异和属性差异状态

因为使用的是ts,所以首先定义一下需要用到的一些类型

touch type.ts
// type.ts
export const isArray = <T extends Array<any>>(type: unknown): type is T => typeof type === 'object' && Array.isArray(type)

export const isUndefined = (type: unknown): type is undefined => type === undefined && typeof type === 'undefined'

export type UndefinedAble<T> = undefined | T

// 这是属性的状态
export enum CompareStatusEnum {
    Create = 'create',
    Delete = 'delete',
    Update = 'update',
    None = 'none'
}

export type CompareDataAttr<T = any> = { [key in KeyType]: T}

// 属性对比的状态结果
export type AttrCompareStatus<T extends Record<string, unknown> = CompareDataAttr> = {
    type: CompareStatusEnum,
    // 属性的属性的状态
    attrStatus?: { [key in keyof T]?: AttrCompareStatus }
}

export type KeyType = string | number | symbol

进行diff对比

touch diffAttr.ts
// 导入类型文件
import { isUndefined, isArray, UndefinedAble, AttrCompareStatus, CompareStatusEnum, KeyType } from './type'

/**
 * 根据属性状态修改status.type
 * @param status
 */
const updateStatusTypeByAttr = (status: AttrCompareStatus): AttrCompareStatus => {
    let type = status.type
    if (status.attrStatus) {
        // 如果全子节点状态一致,则是同种状态,如果状态不s一致,那就是 update
        const keys = Object.keys(status.attrStatus) as Array<keyof typeof status.attrStatus>
        if (!keys.some(it => status.attrStatus![it] === status.attrStatus![0])) {
            type = CompareStatusEnum.Update
        }
    }
    status.type = type
    return status
}

/**
 * 建立 key 与 索引的缓存
 * @param start
 * @param end
 * @param source
 */
const createIdxCache = (start: number, end: number, source: KeyType[]): Map<KeyType, number> => {
    const map: Map<KeyType, number> = new Map()
    for (; start < end; ++start) {
        Reflect.has(source, start) && map.set(source[start], start)
    }
    return map
}

/**
 * 获取两个值对比之后的状态
 * @param originKey
 * @param targetKey
 * @param origin
 * @param target
 */
export const sameValue = <T extends Record<KeyType, unknown>> (originKey: KeyType, targetKey: KeyType, origin: T, target: T): UndefinedAble<AttrCompareStatus> => {
    const originValue = Reflect.get(origin, originKey)
    const targetValue = Reflect.get(target, targetKey)
    if (originValue !== targetValue) {
        const status: AttrCompareStatus = {
            type: CompareStatusEnum.None
        }
        if (typeof originValue === 'object' && typeof targetValue === 'object') {
            // 递归去比较子节点属性的状态
            const { attrStatus } = diffAttr(originValue, targetValue)
            if (attrStatus) {
                if (!Reflect.has(status, 'attrStatus')) {
                    Reflect.set(status, 'attrStatus', {})
                }
                Object.assign(status.attrStatus, attrStatus)
            } else {
                return undefined
            }
        } else {
            status.type = CompareStatusEnum.Update
        }
        return updateStatusTypeByAttr(status)
    }
}

/**
 * 对象的属性对比
 * @param origin
 * @param target
 */
const diffObjAttr = (origin: Record<KeyType, unknown>, target: Record<KeyType, unknown>): AttrCompareStatus => {
    // Reflect.ownKeys 会拿到 get 属性的值
    const originKeys: KeyType[] = Object.keys(origin)
    const targetKeys: KeyType[] = Object.keys(target)
    const filterIdx = new Set()

    const status: AttrCompareStatus = {
        type: CompareStatusEnum.None
    }

    let startOriginKeyIdx = 0,
        startOriginKey = originKeys[0],
        endOriginKeyIdx = originKeys.length - 1,
        endOriginKey = originKeys[originKeys.length - 1]

    let startTargetKeyIdx = 0,
        startTargetKey = targetKeys[0],
        endTargetKeyIdx = targetKeys.length - 1,
        endTargetKey = targetKeys[targetKeys.length - 1]

    let cacheIdMap: UndefinedAble<Map<KeyType, number>>

    /**
     * 比较两个key是否相等
     * @param originKey
     * @param targetKey
     */
    const sameKey = (originKey: KeyType, targetKey: KeyType): boolean => originKey === targetKey

    /**
     * 统一修改状态
     * @param key
     * @param value
     */
    const updateStatus = (key: KeyType, value: AttrCompareStatus): void => {
        if (!Reflect.has(status, 'attrStatus')) {
            Reflect.set(status, 'attrStatus', {})
        }
        // 添加状态
        status.attrStatus![key] = value
    }

    /**
     * 对比key,设置状态
     * @param originKey
     * @param targetKey
     */
    const compare = (originKey: KeyType, targetKey: KeyType): void => {
        // 获取两个状态
        const _status = sameValue(originKey, targetKey, origin, target)
        _status && updateStatus(originKey, _status)
    }

    while (startOriginKeyIdx <= endOriginKeyIdx && startTargetKeyIdx <= endTargetKeyIdx) {
        // 说明当前是查找过后的。直接跳过
        if (filterIdx.has(startTargetKey)) {
            startTargetKey = targetKeys[++startTargetKeyIdx]
        } else if (filterIdx.has(endTargetKey)) {
            endTargetKey = targetKeys[--endTargetKeyIdx]
        } else if (sameKey(startOriginKey, startTargetKey)) {
            compare(startOriginKey, startTargetKey)
            startOriginKey = originKeys[++startOriginKeyIdx]
            startTargetKey = targetKeys[++startTargetKeyIdx]
        } else if (sameKey(endOriginKey, endTargetKey)) {
            compare(endOriginKey, endTargetKey)
            endOriginKey = originKeys[--endOriginKeyIdx]
            endTargetKey = targetKeys[--endTargetKeyIdx]
        } else if (sameKey(endOriginKey, startTargetKey)) {
            compare(endOriginKey, startTargetKey)
            endOriginKey = originKeys[--endOriginKeyIdx]
            startTargetKey = targetKeys[++startTargetKeyIdx]
        } else if (sameKey(startOriginKey, endTargetKey)) {
            compare(startOriginKey, endTargetKey)
            startOriginKey = originKeys[++startOriginKeyIdx]
            endTargetKey = targetKeys[--endTargetKeyIdx]
        } else {
            if (!cacheIdMap) {
                cacheIdMap = createIdxCache(startTargetKeyIdx, endTargetKeyIdx, targetKeys)
            }
            const findIdx = cacheIdMap.get(startOriginKey)
            // 表示找到了
            if (!isUndefined(findIdx)) {
                // 进行对比。如果对比结果没有,则代表只是换了位置并没有更新值
                compare(startOriginKey, targetKeys[findIdx])
                // 添加过滤
                filterIdx.add(startOriginKey)
            } else {
                // 找不到表示为新增
                updateStatus(startOriginKey, { type: CompareStatusEnum.Create })
            }
            startOriginKey = originKeys[++startOriginKeyIdx]
        }
    }

    // target 遍历完成了。剩下的都是新增的
    if (startOriginKeyIdx <= endOriginKeyIdx) {
        for (; startOriginKeyIdx <= endOriginKeyIdx; ++startOriginKeyIdx) updateStatus(originKeys[startOriginKeyIdx], { type: CompareStatusEnum.Create })
    }

    // origin 遍历完成了。剩下的都是删除的
    if (startTargetKeyIdx <= endTargetKeyIdx) {
        for (; startTargetKeyIdx <= endTargetKeyIdx; ++startTargetKeyIdx) updateStatus(targetKeys[startTargetKeyIdx], { type: CompareStatusEnum.Delete })
    }

    return updateStatusTypeByAttr(status)
}

/**
 * 数组的属性对比。数组的每一项使用 diffAttr 对比
 * @param origin
 * @param target
 */
const diffArrayAttr = (origin: Array<any>, target: Array<any>): AttrCompareStatus => {
    let startOriginIdx = 0,
        startOrigin = origin[0]
    const endOriginIdx = origin.length - 1

    let startTargetIdx = 0,
        startTarget = target[0]
    const endTargetIdx = target.length - 1

    const status: AttrCompareStatus = {
        type: CompareStatusEnum.None
    }

    const updateStatus = (key: number, value: AttrCompareStatus): void => {
        if (!Reflect.has(status, 'attrStatus')) {
            Reflect.set(status, 'attrStatus', {})
        }
        // 添加状态
        status.attrStatus![key] = value
    }

    while (startOriginIdx <= endOriginIdx && startTargetIdx <= endTargetIdx) {
        const currentStatus = diffAttr(startOrigin, startTarget)
        // 如果两个相同的话
        if (currentStatus.type === CompareStatusEnum.Update || currentStatus.attrStatus) {
            updateStatus(startTargetIdx, currentStatus)
        }
        startOrigin = origin[++startOriginIdx]
        startTarget = target[++startTargetIdx]
    }

    // target遍历完成,代表有新增的
    if (startOriginIdx <= endOriginIdx) {
        for (; startOriginIdx <= endOriginIdx; ++startOriginIdx) updateStatus(startOriginIdx, { type: CompareStatusEnum.Create })
    }

    // origin遍历完成,代表有删除的
    if (startTargetIdx <= endTargetIdx) {
        // 这里代表着是修改的
        for (; startTargetIdx <= endTargetIdx; ++startTargetIdx) updateStatus(startTargetIdx, { type: CompareStatusEnum.Delete })
    }

    return updateStatusTypeByAttr(status)
}


/**
 * 属性对比,区分数组/对象的对比。。
 * @param origin new
 * @param target old
 */
export const diffAttr = (origin: unknown, target: unknown): AttrCompareStatus => {
    if (typeof origin === 'object' && typeof target === 'object') {
        if (isArray(origin) && isArray(target)) {
            return diffArrayAttr(origin, target)
        }
        return diffObjAttr(origin as Record<KeyType, unknown>, target as Record<KeyType, unknown>)
    } else {
        // 删除
        if (!isUndefined(origin) && isUndefined(target)) {
            return {
                type: CompareStatusEnum.Delete
            }
        } else if (isUndefined(origin) && !isUndefined(target)) { // 新增
            return {
                type: CompareStatusEnum.Create
            }
        }
        return {
            type: origin === target ? CompareStatusEnum.None : CompareStatusEnum.Update
        }
    }
}

注意:当前进行的对比和状态获取都是基于 左侧(origin) 为基准的,也就是说所有的状态都是基于 左侧v 来做对比的,如:左侧(origin) 有,右侧(target) 没有,结果:该属性为删除。

使用

  • 对象
const status = diffAttr({ a: 1, c: 3, e: 4 }, { b: 2, c: 3, e: 5 })
console.log(status)

在这里插入图片描述

  • 数组
const status = diffAttr([{ a: 1 }, { b: 2 }], [{ b: 2, a: 1 }, { a: 11, b: 3 }])
console.log(status)

在这里插入图片描述
因为数组和对象的对比不一致,因为数组存储是无状态的,所以数组只需要按照索引一个一个对比即可,而对象则不一样,对象是有状态的,所以需要获取对象的所以 key 进行 diff 对比,如果两个 key 一致则进行深度对比,并且获取状态。

欢迎大佬们指出不足之处,谢谢~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值