diff
对象和数组的差异比较函数,用于检测数据变化。
函数签名
typescript
function diff(
oldValue: any,
newValue: any,
options?: DiffOptions
): DiffResult
function applyDiff(target: any, changes: DiffItem[]): any
function mergeDiff(...results: DiffResult[]): DiffResult
interface DiffOptions {
/** 是否深度比较 */
deep?: boolean
/** 是否忽略数组顺序 */
ignoreArrayOrder?: boolean
/** 是否包含未改变的项 */
includeUnchanged?: boolean
/** 最大深度 */
maxDepth?: number
/** 自定义比较函数 */
comparator?: (a: any, b: any) => boolean
}
interface DiffResult {
/** 是否有差异 */
hasChanges: boolean
/** 差异项列表 */
changes: DiffItem[]
/** 添加的项 */
added: DiffItem[]
/** 删除的项 */
deleted: DiffItem[]
/** 修改的项 */
modified: DiffItem[]
/** 未改变的项 */
unchanged: DiffItem[]
}
interface DiffItem {
/** 属性路径 */
path: string
/** 差异类型 */
type: 'added' | 'deleted' | 'modified' | 'unchanged'
/** 旧值 */
oldValue?: any
/** 新值 */
newValue?: any
}参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
oldValue | any | 是 | 旧值 |
newValue | any | 是 | 新值 |
options | DiffOptions | 否 | 配置选项 |
options.deep | boolean | 否 | 是否深度比较,默认 true |
options.ignoreArrayOrder | boolean | 否 | 是否忽略数组顺序,默认 false |
options.includeUnchanged | boolean | 否 | 是否包含未改变的项,默认 false |
options.maxDepth | number | 否 | 最大深度,默认 Infinity |
options.comparator | Function | 否 | 自定义比较函数 |
返回值
| 类型 | 说明 |
|---|---|
DiffResult | 差异结果对象,包含所有变化详情 |
工作原理
- 类型检查:判断值的类型(对象、数组、原始类型)
- 递归比较:对于对象和数组,递归比较所有属性和元素
- 差异分类:将差异分为 added、deleted、modified、unchanged 四类
- 路径追踪:记录每个差异项的完整路径
- 深度控制:支持设置最大比较深度,避免无限递归
核心特性:
- 支持对象、数组、原始类型的比较
- 深度递归比较嵌套结构
- 详细的变化追踪(路径、类型、新旧值)
- 灵活的配置选项
- 自定义比较函数支持
基础示例
typescript
import { diff } from 'zcw-shared/functions/utils/diff'
// 简单对象比较
const oldObj = {
name: 'Alice',
age: 25,
city: 'Beijing'
}
const newObj = {
name: 'Alice',
age: 26,
city: 'Shanghai',
country: 'China'
}
const result = diff(oldObj, newObj)
console.log(result.hasChanges) // true
console.log(result.modified) // [{ path: 'age', type: 'modified', oldValue: 25, newValue: 26 }, ...]
console.log(result.added) // [{ path: 'country', type: 'added', newValue: 'China' }]
console.log(result.deleted) // []
// 遍历所有变化
result.changes.forEach(change => {
console.log(`${change.type}: ${change.path}`)
if (change.type === 'modified') {
console.log(` ${change.oldValue} -> ${change.newValue}`)
}
})嵌套对象比较
typescript
const oldData = {
user: {
name: 'Bob',
profile: {
age: 30,
hobbies: ['reading', 'coding']
}
},
settings: {
theme: 'dark'
}
}
const newData = {
user: {
name: 'Bob',
profile: {
age: 31,
hobbies: ['reading', 'gaming', 'coding'],
email: 'bob@example.com'
}
},
settings: {
theme: 'light',
language: 'en'
}
}
const result = diff(oldData, newData, { deep: true })
// 结果包含嵌套路径
// user.profile.age: modified (30 -> 31)
// user.profile.hobbies[1]: modified ('coding' -> 'gaming')
// user.profile.hobbies[2]: added ('coding')
// user.profile.email: added
// settings.theme: modified ('dark' -> 'light')
// settings.language: added数组比较
typescript
const oldArray = [
{ id: 1, name: 'Item 1', price: 100 },
{ id: 2, name: 'Item 2', price: 200 },
{ id: 3, name: 'Item 3', price: 300 }
]
const newArray = [
{ id: 1, name: 'Item 1', price: 150 },
{ id: 2, name: 'Item 2 (Updated)', price: 200 },
{ id: 4, name: 'Item 4', price: 400 }
]
// 按索引比较
const result1 = diff(oldArray, newArray)
// [0].price: modified (100 -> 150)
// [1].name: modified ('Item 2' -> 'Item 2 (Updated)')
// [2]: modified (整个对象变化)
// 忽略顺序比较
const result2 = diff(oldArray, newArray, { ignoreArrayOrder: true })
// 检测元素的添加和删除,不关心顺序配置选项
深度比较
typescript
const old = {
user: {
profile: {
name: 'Alice'
}
}
}
const new_ = {
user: {
profile: {
name: 'Bob'
}
}
}
// 深度比较 (默认)
const result1 = diff(old, new_, { deep: true })
// user.profile.name: modified
// 浅比较
const result2 = diff(old, new_, { deep: false })
// user: modified (整个对象)最大深度
typescript
const old = { a: { b: { c: { d: 1 } } } }
const new_ = { a: { b: { c: { d: 2 } } } }
// 限制深度为 2
const result = diff(old, new_, { maxDepth: 2 })
// a.b: modified (不会深入到 c.d)包含未改变项
typescript
const old = { a: 1, b: 2, c: 3 }
const new_ = { a: 1, b: 20, c: 3 }
const result = diff(old, new_, { includeUnchanged: true })
// a: unchanged (1)
// b: modified (2 -> 20)
// c: unchanged (3)自定义比较函数
typescript
// 忽略大小写比较字符串
const result = diff(
{ name: 'Alice' },
{ name: 'alice' },
{
comparator: (a, b) => {
if (typeof a === 'string' && typeof b === 'string') {
return a.toLowerCase() === b.toLowerCase()
}
return a === b
}
}
)
// hasChanges: false (忽略了大小写)应用差异
typescript
import { diff, applyDiff } from 'zcw-shared/functions/utils/diff'
const oldData = { name: 'Alice', age: 25 }
const newData = { name: 'Alice', age: 26, city: 'Beijing' }
// 1. 计算差异
const result = diff(oldData, newData)
// 2. 应用差异到旧数据
const updated = applyDiff(oldData, result.changes)
console.log(updated)
// { name: 'Alice', age: 26, city: 'Beijing' }
// 3. 也可以只应用部分差异
const partialChanges = result.changes.filter(c => c.path === 'age')
const partialUpdate = applyDiff(oldData, partialChanges)
console.log(partialUpdate)
// { name: 'Alice', age: 26 }合并多个差异
typescript
import { diff, mergeDiff } from 'zcw-shared/functions/utils/diff'
const v1 = { a: 1, b: 2, c: 3 }
const v2 = { a: 10, b: 2, c: 3 }
const v3 = { a: 10, b: 20, c: 3 }
const diff1 = diff(v1, v2) // a: 1 -> 10
const diff2 = diff(v2, v3) // b: 2 -> 20
// 合并差异
const merged = mergeDiff(diff1, diff2)
// a: 1 -> 10
// b: 2 -> 20实际应用场景
表单验证
typescript
// 检测表单是否有修改
const originalFormData = {
username: 'alice',
email: 'alice@example.com',
profile: {
bio: 'Hello',
age: 25
}
}
const currentFormData = {
username: 'alice',
email: 'alice@gmail.com',
profile: {
bio: 'Hello World',
age: 25
}
}
const result = diff(originalFormData, currentFormData)
if (result.hasChanges) {
console.log('表单已修改,需要保存')
console.log('修改的字段:', result.modified.map(c => c.path))
// ['email', 'profile.bio']
} else {
console.log('表单未修改')
}状态同步
typescript
// 检测状态变化并同步到服务器
let localState = { count: 0, user: { name: 'Alice' } }
let serverState = { count: 0, user: { name: 'Alice' } }
function syncToServer() {
const result = diff(serverState, localState)
if (result.hasChanges) {
// 只发送变化的数据
const changes = result.changes
.filter(c => c.type !== 'unchanged')
.reduce((acc, c) => {
acc[c.path] = c.newValue
return acc
}, {} as any)
// 发送到服务器
fetch('/api/sync', {
method: 'POST',
body: JSON.stringify({ changes })
})
serverState = { ...localState }
}
}数据审计
typescript
// 记录数据变更历史
class DataAuditor {
private history: Array<{
timestamp: number
changes: DiffItem[]
}> = []
recordChange(oldData: any, newData: any) {
const result = diff(oldData, newData)
if (result.hasChanges) {
this.history.push({
timestamp: Date.now(),
changes: result.changes
})
}
}
getHistory() {
return this.history.map(entry => ({
time: new Date(entry.timestamp).toLocaleString(),
changes: entry.changes.map(c => ({
field: c.path,
action: c.type,
from: c.oldValue,
to: c.newValue
}))
}))
}
}
const auditor = new DataAuditor()
auditor.recordChange(
{ name: 'Alice', age: 25 },
{ name: 'Alice', age: 26 }
)配置更新检测
typescript
// 检测配置文件变化
const defaultConfig = {
server: {
port: 3000,
host: 'localhost'
},
database: {
url: 'mongodb://localhost:27017',
name: 'mydb'
}
}
const userConfig = {
server: {
port: 8080,
host: '0.0.0.0'
},
database: {
url: 'mongodb://localhost:27017',
name: 'mydb'
}
}
const result = diff(defaultConfig, userConfig)
console.log('用户自定义的配置项:')
result.modified.forEach(change => {
console.log(` ${change.path}: ${change.oldValue} -> ${change.newValue}`)
})
// server.port: 3000 -> 8080
// server.host: localhost -> 0.0.0.0优化 API 请求
typescript
// 只发送变化的字段
async function updateUser(userId: string, changes: any) {
const currentUser = await fetchUser(userId)
const result = diff(currentUser, changes)
if (!result.hasChanges) {
console.log('无需更新')
return currentUser
}
// 构建 PATCH 请求体(只包含变化的字段)
const patchData: any = {}
result.modified.concat(result.added).forEach(change => {
patchData[change.path] = change.newValue
})
// 发送 PATCH 请求
return await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patchData)
})
}高级用法
自定义差异处理
typescript
// 根据差异类型执行不同操作
function handleDiff(result: DiffResult) {
// 处理新增项
result.added.forEach(item => {
console.log(`新增字段 ${item.path}:`, item.newValue)
// 发送通知、更新 UI 等
})
// 处理删除项
result.deleted.forEach(item => {
console.log(`删除字段 ${item.path}:`, item.oldValue)
// 确认删除、备份数据等
})
// 处理修改项
result.modified.forEach(item => {
console.log(`修改字段 ${item.path}:`, item.oldValue, '->', item.newValue)
// 验证新值、记录历史等
})
}差异可视化
typescript
// 生成差异报告
function generateDiffReport(result: DiffResult): string {
let report = '差异报告\n'
report += '='.repeat(50) + '\n\n'
if (!result.hasChanges) {
report += '无差异\n'
return report
}
report += `总计: ${result.changes.length} 项变化\n`
report += ` 新增: ${result.added.length}\n`
report += ` 删除: ${result.deleted.length}\n`
report += ` 修改: ${result.modified.length}\n\n`
result.changes.forEach(change => {
report += `[${change.type.toUpperCase()}] ${change.path}\n`
if (change.type === 'modified') {
report += ` ${change.oldValue} -> ${change.newValue}\n`
} else if (change.type === 'added') {
report += ` + ${change.newValue}\n`
} else if (change.type === 'deleted') {
report += ` - ${change.oldValue}\n`
}
})
return report
}条件过滤
typescript
// 只关注特定字段的变化
function diffFields(oldData: any, newData: any, fields: string[]) {
const result = diff(oldData, newData)
return {
...result,
changes: result.changes.filter(c =>
fields.some(field => c.path.startsWith(field))
)
}
}
// 使用示例
const result = diffFields(
oldUser,
newUser,
['profile.', 'settings.'] // 只关注 profile 和 settings
)性能优化
typescript
// 1. 使用浅比较提高性能
const result = diff(largeObject1, largeObject2, { deep: false })
// 2. 限制比较深度
const result = diff(deepObject1, deepObject2, { maxDepth: 3 })
// 3. 自定义比较函数优化特定场景
const result = diff(data1, data2, {
comparator: (a, b) => {
// 对于大数组,使用长度比较作为快速检查
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false
}
return deepEqual(a, b)
}
})实用的差异比较工具,让数据变化追踪变得简单高效!