Skip to content
hard
复制引用处理

实现一个 深拷贝

示例

要求能够解决循环嵌套对象的深拷贝函数

js
// 测试代码
const a = { name: 'Alice' }
a.self = a // 循环引用

const b = deepClone(a)
console.log(b) // 输出: { name: 'Alice', self: [Circular] }
/*
console.log 的输出结果中包含 [Circular],
这是因为 JavaScript 的控制台会检测到对象中的循环引用,
并用这个标记来表示,避免陷入无限循环。
事实上,我们的 deepClone 函数并不会在实际对象数据结构中插入 [Circular] 这类标记,
它只是一个视觉化的提示,帮助我们理解输出结果。
 */

题目模版

js
/**
 * 深拷贝一个对象或数组。
 *
 * @param {any} obj - 要深拷贝的对象或数组。
 * @param {Map} [cache] - 用于存储已拷贝对象的缓存,防止循环引用。
 * @returns {any} 返回深拷贝后的对象或数组。
 */
export default function deepClone(obj, cache = new Map()) {

}
ts
type Cache = Map<object, any>

export default function deepClone<T>(obj: T, cache: Cache = new Map()): T {

}

测试代码

js
import { describe, expect, it } from 'vitest'
import deepClone from './deepClone'

describe('deepClone function', () => {
  it('应在传入基本数据类型时正常运行', () => {
    expect(deepClone(1)).toBe(1)
    expect(deepClone('string')).toBe('string')
    expect(deepClone(null)).toBe(null)
    expect(deepClone(undefined)).toBe(undefined)
    expect(deepClone(true)).toBe(true)
  })

  it('应在传入对象时正常运行', () => {
    const obj = { a: 1, b: { c: 2 } }
    const clone = deepClone(obj)
    expect(clone).not.toBe(obj)
    expect(clone).toEqual(obj)
  })

  it('应在处理数组时正常运行', () => {
    const arr = [1, 2, { a: 3 }]
    const clone = deepClone(arr)
    expect(clone).not.toBe(arr)
    expect(clone).toEqual(arr)
  })

  it('应在处理Date对象时正常运行', () => {
    const date = new Date()
    const clone = deepClone(date)
    expect(clone).not.toBe(date)
    expect(clone.getTime()).toBe(date.getTime())
  })

  it('应在处理RegExp对象时正常运行', () => {
    const regex = /abc/gi
    const clone = deepClone(regex)
    expect(clone).not.toBe(regex)
    expect(clone.source).toBe(regex.source)
    expect(clone.flags).toBe(regex.flags)
  })

  it('应在处理Symbol时正常运行', () => {
    const sym = Symbol('desc')
    const clone = deepClone(sym)
    expect(clone).not.toBe(sym)
  })

  it('应在处理循环引用时正常运行', () => {
    const obj = { name: 'Alice' }
    obj.self = obj // 循环引用
    const clone = deepClone(obj)
    expect(clone).not.toBe(obj)
    expect(clone.name).toBe('Alice')
    expect(clone.self).toBe(clone) // 确保循环引用被正确复制
  })

  it('应在处理复杂嵌套结构时正常运行', () => {
    const complexObj = {
      date: new Date(),
      regEx: /test/i,
      symbolKey: Symbol('key'),
      nested: {
        arr: [1, 2, 3],
        map: new Map([
          ['key1', 'value1'],
          ['key2', 'value2'],
        ]),
        set: new Set([1, 2, 3]),
        arrayWithObj: [
          { 1: 'jack' },
          2,
          3,
          { a: 1, b: 2, c: { d: /111/g, b: [1, 2, 3, Symbol(1)] } },
        ],
        [Symbol('symbol key')]: {
          [Symbol('1')]: [1, 2, 3, {}],
        },
      },
    }

    const clone = deepClone(complexObj)
    expect(clone).not.toBe(complexObj)
    expect(clone.nested.arr).toEqual([1, 2, 3])
  })
  it('应在对象包含方法时正常运行,并保持方法的功能', () => {
    const objWithMethod = {
      value: 42,
      getValue() {
        return this.value
      },
    }

    const clonedObjWithMethod = deepClone(objWithMethod)

    // 检查克隆后的对象结构
    expect(clonedObjWithMethod).not.toBe(objWithMethod)
    expect(typeof clonedObjWithMethod.getValue).toBe('function')

    // 检查克隆后的方法是否工作正常
    expect(clonedObjWithMethod.getValue()).toBe(42)

    // 修改克隆对象的值并检查方法输出
    clonedObjWithMethod.value = 100
    expect(clonedObjWithMethod.getValue()).toBe(100)

    // 原始对象应保持不变
    expect(objWithMethod.getValue()).toBe(42)
  })
  it('应在对象包含方法时正常运行,并保持方法的功能以及方法的属性', () => {
    const objWithMethod = {
      value: 42,
      getValue() {
        return this.value
      },
    }
    objWithMethod.getValue.a = objWithMethod
    const clonedObjWithMethod = deepClone(objWithMethod)

    // 检查克隆后的对象结构
    expect(clonedObjWithMethod).not.toBe(objWithMethod)
    expect(clonedObjWithMethod.getValue.a.value).toBe(42)

    // 检查克隆后的方法是否工作正常
    expect(clonedObjWithMethod.getValue()).toBe(42)

    // 修改克隆对象的值并检查方法输出
    clonedObjWithMethod.value = 100
    expect(clonedObjWithMethod.getValue()).toBe(100)

    // 原始对象应保持不变
    expect(objWithMethod.getValue()).toBe(42)
  })

  it('应确保深拷贝后的对象与原始对象不同,并且只包含原始对象上的key值', () => {
    function Parent() {
      this.a = 1
    }
    Parent.prototype.b = 2

    const obj = new Parent()
    const symKey = Symbol('sym')
    obj[symKey] = 'symbolValue'

    const clone = deepClone(obj)

    // 检查克隆后的对象与原始对象不同
    expect(clone).not.toBe(obj)

    // 检查克隆后的对象只包含原始对象上的key值
    expect(clone).toHaveProperty('a', 1)
    expect(clone).not.toHaveProperty('b') // 原型上的属性不应被拷贝

    // 检查symbol键是否被正确拷贝
    expect(clone[symKey]).toBe('symbolValue')
  })
})
ts
import { describe, expect, it } from 'vitest'
import deepClone from './deepClone'

describe('deepClone function', () => {
  it('应在传入基本数据类型时正常运行', () => {
    expect(deepClone(1)).toBe(1)
    expect(deepClone('string')).toBe('string')
    expect(deepClone(null)).toBe(null)
    expect(deepClone(undefined)).toBe(undefined)
    expect(deepClone(true)).toBe(true)
  })

  it('应在传入对象时正常运行', () => {
    const obj = { a: 1, b: { c: 2 } }
    const clone = deepClone(obj)
    expect(clone).not.toBe(obj)
    expect(clone).toEqual(obj)
  })

  it('应在处理数组时正常运行', () => {
    const arr = [1, 2, { a: 3 }]
    const clone = deepClone(arr)
    expect(clone).not.toBe(arr)
    expect(clone).toEqual(arr)
  })

  it('应在处理Date对象时正常运行', () => {
    const date = new Date()
    const clone = deepClone(date)
    expect(clone).not.toBe(date)
    expect(clone.getTime()).toBe(date.getTime())
  })

  it('应在处理RegExp对象时正常运行', () => {
    const regex = /abc/gi
    const clone = deepClone(regex)
    expect(clone).not.toBe(regex)
    expect(clone.source).toBe(regex.source)
    expect(clone.flags).toBe(regex.flags)
  })

  it('应在处理Symbol时正常运行', () => {
    const sym = Symbol('desc')
    const clone = deepClone(sym)
    expect(clone).not.toBe(sym)
  })

  it('应在处理循环引用时正常运行', () => {
    const obj: Record<string, string | object> = { name: 'Alice' }
    obj.self = obj // 循环引用
    const clone = deepClone(obj)
    expect(clone).not.toBe(obj)
    expect(clone.name).toBe('Alice')
    expect(clone.self).toBe(clone) // 确保循环引用被正确复制
  })

  it('应在处理复杂嵌套结构时正常运行', () => {
    const complexObj = {
      date: new Date(),
      regEx: /test/i,
      symbolKey: Symbol('key'),
      nested: {
        arr: [1, 2, 3],
        map: new Map([
          ['key1', 'value1'],
          ['key2', 'value2'],
        ]),
        set: new Set([1, 2, 3]),
        arrayWithObj: [
          { 1: 'jack' },
          2,
          3,
          { a: 1, b: 2, c: { d: /111/g, b: [1, 2, 3, Symbol(1)] } },
        ],
        [Symbol('symbol key')]: {
          [Symbol('1')]: [1, 2, 3, {}],
        },
      },
    }

    const clone = deepClone(complexObj)
    expect(clone).not.toBe(complexObj)
    expect(clone.nested.arr).toEqual([1, 2, 3])
  })
  it('应在对象包含方法时正常运行,并保持方法的功能', () => {
    const objWithMethod = {
      value: 42,
      getValue() {
        return this.value
      },
    }

    const clonedObjWithMethod = deepClone(objWithMethod)

    // 检查克隆后的对象结构
    expect(clonedObjWithMethod).not.toBe(objWithMethod)
    expect(typeof clonedObjWithMethod.getValue).toBe('function')

    // 检查克隆后的方法是否工作正常
    expect(clonedObjWithMethod.getValue()).toBe(42)

    // 修改克隆对象的值并检查方法输出
    clonedObjWithMethod.value = 100
    expect(clonedObjWithMethod.getValue()).toBe(100)

    // 原始对象应保持不变
    expect(objWithMethod.getValue()).toBe(42)
  })
  it('应在对象包含方法时正常运行,并保持方法的功能以及方法的属性', () => {
    const objWithMethod = {
      value: 42,
      getValue() {
        return this.value
      },
    };
    (objWithMethod.getValue as any).a = objWithMethod
    const clonedObjWithMethod = deepClone(objWithMethod)

    // 检查克隆后的对象结构
    expect(clonedObjWithMethod).not.toBe(objWithMethod)
    expect((clonedObjWithMethod.getValue as any).a.value).toBe(42)

    // 检查克隆后的方法是否工作正常
    expect(clonedObjWithMethod.getValue()).toBe(42)

    // 修改克隆对象的值并检查方法输出
    clonedObjWithMethod.value = 100
    expect(clonedObjWithMethod.getValue()).toBe(100)

    // 原始对象应保持不变
    expect(objWithMethod.getValue()).toBe(42)
  })

  it('应确保深拷贝后的对象与原始对象不同,并且只包含原始对象上的key值', () => {
    function Parent(this: { a: number }) {
      this.a = 1
    }
    Parent.prototype.b = 2

    const obj = new (Parent as any)()
    const symKey = Symbol('sym')
    obj[symKey] = 'symbolValue'

    const clone = deepClone(obj)

    // 检查克隆后的对象与原始对象不同
    expect(clone).not.toBe(obj)

    // 检查克隆后的对象只包含原始对象上的key值
    expect(clone).toHaveProperty('a', 1)
    expect(clone).not.toHaveProperty('b') // 原型上的属性不应被拷贝

    // 检查symbol键是否被正确拷贝
    expect(clone[symKey]).toBe('symbolValue')
  })
})

答案

类型路径
JS 版本problems/Day 05/answer.js
TS 版本problems/Day 05/ts/answer.ts
Review05.md

内容基于 MIT 许可 | 保持节奏 · 持续积累