Skip to content

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
}

参数

参数名类型必填说明
oldValueany旧值
newValueany新值
optionsDiffOptions配置选项
options.deepboolean是否深度比较,默认 true
options.ignoreArrayOrderboolean是否忽略数组顺序,默认 false
options.includeUnchangedboolean是否包含未改变的项,默认 false
options.maxDepthnumber最大深度,默认 Infinity
options.comparatorFunction自定义比较函数

返回值

类型说明
DiffResult差异结果对象,包含所有变化详情

工作原理

  1. 类型检查:判断值的类型(对象、数组、原始类型)
  2. 递归比较:对于对象和数组,递归比较所有属性和元素
  3. 差异分类:将差异分为 added、deleted、modified、unchanged 四类
  4. 路径追踪:记录每个差异项的完整路径
  5. 深度控制:支持设置最大比较深度,避免无限递归

核心特性

  • 支持对象、数组、原始类型的比较
  • 深度递归比较嵌套结构
  • 详细的变化追踪(路径、类型、新旧值)
  • 灵活的配置选项
  • 自定义比较函数支持

基础示例

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)
  }
})

实用的差异比较工具,让数据变化追踪变得简单高效!