{T}

对象拷贝

对象拷贝是 JavaScript 中常见的操作,分为浅拷贝和深拷贝。理解它们的区别对于正确处理数据至关重要。

赋值 vs 拷贝

赋值(引用)

赋值只是传递引用,不会创建新对象。

javascript
const obj1 = { a: 1, b: 2 }
const obj2 = obj1

obj2.a = 10
console.log(obj1.a)  // 10(原对象被修改)

console.log(obj1 === obj2)  // true(指向同一对象)

内存示意:

code
栈内存          堆内存
┌──────────┐           ┌─────────────┐
│ obj1: 0x1 ├─────────→│ { a: 10,    │
├──────────┤           │   b: 2 }    │
│ obj2: 0x1 ├─────────→│             │
└──────────┘           └─────────────┘

浅拷贝

浅拷贝创建新对象,但嵌套对象仍然是引用。

javascript
const obj1 = { a: 1, b: { c: 2 } }
const obj2 = { ...obj1 }

obj2.a = 10
obj2.b.c = 20

console.log(obj1.a)   // 1(第一层独立)
console.log(obj1.b.c) // 20(嵌套对象共享)

console.log(obj1 === obj2)        // false
console.log(obj1.b === obj2.b)    // true(嵌套对象相同)

内存示意:

code
栈内存          堆内存
┌──────────┐           ┌─────────────┐
│ obj1: 0x1 ├─────────→│ { a: 1,     │
├──────────┤           │   b: 0x3 }  │──┐
│ obj2: 0x2 ├─────┐    └─────────────┘  │
└──────────┘     │                      │
                 │    ┌─────────────┐   │
                 └───→│ { a: 10,    │   │
                      │   b: 0x3 }  │───┘
                      └─────────────┘
                           │
                      ┌────▼────┐
                      │ { c: 20 }│ (共享)
                      └──────────┘

深拷贝

深拷贝递归复制所有层级,创建完全独立的对象。

javascript
const obj1 = { a: 1, b: { c: 2 } }
const obj2 = JSON.parse(JSON.stringify(obj1))

obj2.b.c = 20
console.log(obj1.b.c)  // 2(原对象不受影响)

console.log(obj1.b === obj2.b)  // false(完全独立)

内存示意:

code
栈内存          堆内存
┌──────────┐           ┌─────────────┐
│ obj1: 0x1 ├─────────→│ { a: 1,     │
├──────────┤           │   b: 0x3 }  │──┐
│ obj2: 0x2 ├─────┐    └─────────────┘  │
└──────────┘     │                      │
                 │    ┌─────────────┐   │    ┌──────────┐
                 └───→│ { a: 1,     │   └───→│ { c: 2 } │
                      │   b: 0x4 }  │───────→└──────────┘
                      └─────────────┘             │
                                                 │    ┌──────────┐
                                                 └───→│ { c: 20 }│
                                                      └──────────┘

浅拷贝 ⚠️

浅拷贝只复制对象的第一层属性,嵌套对象仍然是引用。

方法 1:Object.assign()

Object.assign(target, ...sources) 将所有可枚举属性从一个或多个源对象复制到目标对象。

javascript
const obj = { a: 1, b: { c: 2 } }
const copy = Object.assign({}, obj)

copy.a = 10
copy.b.c = 20

console.log(obj.a)   // 1
console.log(obj.b.c) // 20(嵌套对象被修改)

// 合并多个对象
const merged = Object.assign({}, { a: 1 }, { b: 2 }, { c: 3 })
console.log(merged)  // { a: 1, b: 2, c: 3 }

// 注意:后面的属性会覆盖前面的
const override = Object.assign({}, { a: 1, b: 1 }, { b: 2, c: 3 })
console.log(override)  // { a: 1, b: 2, c: 3 }

方法 2:扩展运算符

ES6 扩展运算符 {...obj} 是最简洁的浅拷贝方式。

javascript
const obj = { a: 1, b: { c: 2 } }
const copy = { ...obj }

copy.a = 10
copy.b.c = 20

console.log(obj.a)   // 1
console.log(obj.b.c) // 20(嵌套对象被修改)

// 合并对象(更优雅)
const merged = { ...{ a: 1 }, ...{ b: 2 }, ...{ c: 3 } }
console.log(merged)  // { a: 1, b: 2, c: 3 }

// 添加/覆盖属性
const extended = { ...obj, d: 4, a: 100 }
console.log(extended)  // { a: 100, b: { c: 20 }, d: 4 }

方法 3:Array.prototype.slice()

slice() 方法返回数组的一部分的浅拷贝。

javascript
const arr = [1, 2, { a: 3 }]
const copy = arr.slice()

copy[0] = 10
copy[2].a = 30

console.log(arr[0])    // 1
console.log(arr[2].a)  // 30(嵌套对象被修改)

// 常用于数组拷贝
const fullCopy = arr.slice()  // 复制全部
const partialCopy = arr.slice(1, 3)  // 复制索引 1-2

方法 4:Array.prototype.concat()

concat() 方法用于合并数组,返回新数组。

javascript
const arr = [1, 2, { a: 3 }]
const copy = arr.concat()

copy[0] = 10
copy[2].a = 30

console.log(arr[0])    // 1
console.log(arr[2].a)  // 30(嵌套对象被修改)

// 合并多个数组
const merged = [1, 2].concat([3, 4], [5, 6])
console.log(merged)  // [1, 2, 3, 4, 5, 6]

方法 5:Array.from()

Array.from() 从可迭代或类数组对象创建新数组。

javascript
const arr = [1, 2, { a: 3 }]
const copy = Array.from(arr)

copy[0] = 10
copy[2].a = 30

console.log(arr[0])    // 1
console.log(arr[2].a)  // 30(嵌套对象被修改)

// 转换类数组
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
const arr2 = Array.from(arrayLike)  // ['a', 'b']

// 使用 map 函数
const doubled = Array.from([1, 2, 3], x => x * 2)
console.log(doubled)  // [2, 4, 6]

方法 6:Object.create()

使用 Object.create() 可以创建一个新对象,使用现有对象作为原型。

javascript
const obj = { a: 1, b: { c: 2 } }
const copy = Object.create(
  Object.getPrototypeOf(obj), 
  Object.getOwnPropertyDescriptors(obj)
)

copy.a = 10
copy.b.c = 20

console.log(obj.a)   // 1
console.log(obj.b.c) // 20(嵌套对象被修改)

// 特点:可以复制 getter/setter 和不可枚举属性
const original = {
  get value() { return this._value },
  set value(v) { this._value = v }
}
original._value = 10

const clone = Object.create(
  Object.getPrototypeOf(original),
  Object.getOwnPropertyDescriptors(original)
)

console.log(clone.value)  // 10(getter 被保留)

深拷贝 ⚠️

深拷贝递归复制所有层级的属性,创建完全独立的对象。

方法 1:JSON.parse(JSON.stringify())

利用 JSON 序列化和反序列化实现深拷贝。

javascript
const obj = {
  a: 1,
  b: { c: 2 },
  date: new Date(),
  regex: /test/,
  func: function () { return 'hello' },
  undef: undefined,
  symbol: Symbol('key')
}

const copy = JSON.parse(JSON.stringify(obj))

console.log(copy.a)   // 1
console.log(copy.b.c) // 2
console.log(copy.date)      // 字符串 "2024-01-01T00:00:00.000Z",不是 Date 对象
console.log(copy.regex)     // {},正则表达式丢失
console.log(copy.func)      // undefined,函数丢失
console.log(copy.undef)     // undefined,但属性也被删除了
console.log(copy.symbol)    // undefined,Symbol 丢失

限制:

数据类型处理结果
undefined被忽略(属性删除)
Symbol被忽略(属性删除)
Function被忽略(属性删除)
Date转为字符串
RegExp转为空对象 {}
Map/Set转为 {}
Error转为 {}
BigInt抛出错误
循环引用抛出错误
不可枚举属性被忽略

适用场景:

  • 纯数据对象(无特殊对象类型)
  • 无循环引用
  • 无函数、Symbol、undefined

方法 2:递归实现

手动实现递归深拷贝,可以处理各种特殊情况。

javascript
function deepClone(obj, hash = new WeakMap()) {
  // 处理 null 和非对象类型
  if (obj === null || typeof obj !== 'object') {
    return obj
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj)
  }
  
  // 处理特殊对象

  // ... 中间省略 ...

console.log(copy.a)     // 1
console.log(copy.b.c)   // 2
console.log(copy.date instanceof Date)  // true
console.log(copy.regex instanceof RegExp)  // true
console.log(copy.func())  // 'hello'
console.log(copy.self === copy)  // true

方法 3:structuredClone()(现代浏览器)

浏览器原生提供的深拷贝 API,性能好且支持多种类型。

javascript
const obj = {
  a: 1,
  b: { c: 2 },
  date: new Date(),
  regex: /test/,
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  arrayBuffer: new ArrayBuffer(8),
  blob: new Blob(['hello'])
}

const copy = structuredClone(obj)

console.log(copy.date instanceof Date)  // true
console.log(copy.map instanceof Map)    // true
console.log(copy.set instanceof Set)    // true
console.log(copy.arrayBuffer instanceof ArrayBuffer)  // true
console.log(copy.blob instanceof Blob)  // true

// 支持循环引用
obj.self = obj
const copy2 = structuredClone(obj)
console.log(copy2.self === copy2)  // true

支持的数据类型:

类型支持说明
原始类型string, number, boolean, null, undefined, BigInt
Date完整支持
RegExp但 lastIndex 不保留
Map/Set完整支持
ArrayBuffer完整支持
TypedArrayInt8Array, Uint8Array 等
Blob/File完整支持

// ... 中间省略 ...

方法 4:第三方库

Lodash.cloneDeep()

Lodash 的 cloneDeep 是生产环境中最常用的深拷贝方案。

code
import _ from 'lodash'

const obj = {
  a: 1,
  b: { c: 2 },
  date: new Date(),
  regex: /test/,
  func: function () { return 'hello' },
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3])
}

obj.self = obj  // 循环引用

const copy = _.cloneDeep(obj)

console.log(copy.date instanceof Date)  // true
console.log(copy.regex instanceof RegExp)  // true
console.log(copy.func())  // 'hello'
console.log(copy.map instanceof Map)  // true
console.log(copy.self === copy)  // true

优势:

  • 功能全面,处理各种边界情况
  • 经过充分测试,稳定可靠
  • 支持自定义拷贝逻辑

其他库

javascript
// Ramda
import { clone } from 'ramda'
const copy = clone(obj)

// immer(不可变数据)
import { produce } from 'immer'
const copy = produce(obj, draft => draft)

拷贝层级可视化

code
原始对象                  赋值                    浅拷贝                  深拷贝
┌────────────┐         ┌────────────┐         ┌────────────┐         ┌────────────┐
│ { a: 1,    │         │ { a: 1,    │         │ { a: 1,    │         │ { a: 1,    │
│   b: {     │         │   b: {     │         │   b: {     │         │   b: {     │
│     c: 2   │         │     c: 2   │         │     c: 2   │         │     c: 2   │
│   }        │         │   }        │         │   }        │         │   }        │
│ }          │         │ }          │         │ }          │         │ }          │
└────────────┘         └────────────┘         └────────────┘         └────────────┘
     ↓                      ↓                      ↓                      ↓
指向同一对象              指向同一对象            创建新对象              创建完全独立对象
                                                
修改互相影响              修改互相影响            第一层独立              所有层级独立
                                                嵌套对象共享            嵌套对象独立

深拷贝的完整实现

以下是一个生产级别的深拷贝实现,处理了各种边界情况:

javascript
function deepClone(obj, hash = new WeakMap()) {
  // 处理 null 和 undefined
  if (obj === null || obj === undefined) {
    return obj
  }
  
  // 处理原始类型
  if (typeof obj !== 'object') {
    return obj
  }
  
  // 处理循环引用

  // ... 中间省略 ...

console.log(cloned.arr[2].a)   // 3
console.log(cloned.map instanceof Map)  // true
console.log(cloned.set instanceof Set)  // true
console.log(cloned.func())     // 'hello'
console.log(cloned.self === cloned)  // true
console.log(cloned.nonEnum)    // 'non-enumerable'

性能测试

以下是不同拷贝方法的性能对比测试:

javascript
// 性能测试函数
function benchmark(name, fn, obj, iterations = 10000) {
  const start = performance.now()
  for (let i = 0; i < iterations; i++) {
    fn(obj)
  }
  const end = performance.now()
  console.log(`${name}: ${(end - start).toFixed(2)}ms`)
}

// 测试对象
const simpleObj = { a: 1, b: 2, c: 3 }

  // ... 中间省略 ...


console.log('\n=== 深拷贝性能(复杂对象)===')
benchmark('JSON 方法', obj => JSON.parse(JSON.stringify(obj)), complexObj)
benchmark('structuredClone', structuredClone, complexObj)
benchmark('递归实现', deepClone, complexObj)
benchmark('Lodash.cloneDeep', _.cloneDeep, complexObj)

性能测试结果(10000 次迭代):

code
=== 浅拷贝性能 ===
Object.assign(): 15.23ms
扩展运算符: 12.45ms

=== 深拷贝性能(简单对象)===
JSON 方法: 45.67ms
structuredClone: 38.92ms
递归实现: 125.34ms
Lodash.cloneDeep: 98.76ms

=== 深拷贝性能(复杂对象)===
JSON 方法: 156.78ms
structuredClone: 123.45ms
递归实现: 345.67ms
Lodash.cloneDeep: 289.34ms

性能结论:

方法性能等级适用场景
扩展运算符⭐⭐⭐⭐⭐浅拷贝首选
Object.assign()⭐⭐⭐⭐浅拷贝(合并多对象)
structuredClone()⭐⭐⭐⭐深拷贝首选(现代浏览器)
JSON 方法⭐⭐⭐简单对象深拷贝
Lodash.cloneDeep()⭐⭐⭐生产环境深拷贝
递归实现⭐⭐需要自定义逻辑

拷贝方法对比

方法拷贝类型函数循环引用特殊对象不可枚举Symbol性能
赋值引用⭐⭐⭐⭐⭐
Object.assign()⭐⭐⭐⭐
扩展运算符⭐⭐⭐⭐⭐
Array.slice()⭐⭐⭐⭐
JSON 方法⭐⭐⭐
structuredClone()⭐⭐⭐⭐
递归实现⭐⭐
Lodash.cloneDeep()⭐⭐⭐

边界情况与陷阱

1. 循环引用

javascript
const obj = { a: 1 }
obj.self = obj

// JSON 方法会报错
try {
  JSON.parse(JSON.stringify(obj))
} catch (e) {
  console.error(e)  // TypeError: Converting circular structure to JSON
}

// structuredClone 可以处理
const copy1 = structuredClone(obj)
console.log(copy1.self === copy1)  // true

// 递归实现需要特殊处理
const copy2 = deepClone(obj)
console.log(copy2.self === copy2)  // true

2. 特殊数值

javascript
const obj = {
  nan: NaN,
  infinity: Infinity,
  negInfinity: -Infinity,
  zero: -0
}

// JSON 方法
const copy1 = JSON.parse(JSON.stringify(obj))
console.log(copy1.nan)        // null(NaN 变成 null)
console.log(copy1.infinity)   // null(Infinity 变成 null)
console.log(copy1.negInfinity) // null
console.log(copy1.zero)       // 0(-0 变成 0)

// structuredClone
const copy2 = structuredClone(obj)
console.log(Object.is(copy2.nan, NaN))  // true(保留 NaN)
console.log(copy2.infinity)   // Infinity(保留)
console.log(copy2.zero)       // -0(保留)

3. getter/setter

javascript
const obj = {
  _value: 10,
  get value() { return this._value },
  set value(v) { this._value = v }
}

// 扩展运算符和 Object.assign 会调用 getter
const copy1 = { ...obj }
console.log(copy1.value)  // 10
copy1.value = 20
console.log(copy1.value)  // 20(变成了普通属性)
console.log(copy1._value) // 10(getter/setter 丢失)

// JSON 方法会调用 getter
const copy2 = JSON.parse(JSON.stringify(obj))
console.log(copy2.value)  // 10(但 getter/setter 丢失)

// 深拷贝保留 getter/setter
const copy3 = deepClone(obj)
console.log(copy3.value)  // 10
copy3.value = 20
console.log(copy3._value) // 20(getter/setter 保留)

4. 原型链

javascript
function Person(name) {
  this.name = name
}
Person.prototype.greet = function () {
  console.log(`Hello, ${this.name}`)
}

const person = new Person('Alice')

// JSON 方法丢失原型
const copy1 = JSON.parse(JSON.stringify(person))
console.log(copy1 instanceof Person)  // false
console.log(copy1.greet)  // undefined

// structuredClone 丢失原型
const copy2 = structuredClone(person)
console.log(copy2 instanceof Person)  // false

// 深拷贝保留原型
const copy3 = deepClone(person)
console.log(copy3 instanceof Person)  // true
copy3.greet()  // 'Hello, Alice'

5. 不可枚举属性

javascript
const obj = { a: 1 }
Object.defineProperty(obj, 'b', {
  value: 2,
  enumerable: false
})

// 扩展运算符不复制不可枚举属性
const copy1 = { ...obj }
console.log(copy1.b)  // undefined

// JSON 方法也不复制
const copy2 = JSON.parse(JSON.stringify(obj))
console.log(copy2.b)  // undefined

// 深拷贝可以复制
const copy3 = deepClone(obj)
console.log(copy3.b)  // 2

6. 冻结对象

javascript
const obj = Object.freeze({ a: 1, b: { c: 2 } })

// 拷贝后不再是冻结状态
const copy = { ...obj }
console.log(Object.isFrozen(copy))  // false

// 如果需要保持冻结状态
const frozenCopy = Object.freeze({ ...obj })
console.log(Object.isFrozen(frozenCopy))  // true

实际应用场景

1. 状态管理(React/Vue)

javascript
// React 状态更新
import { useState } from 'react'

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', completed: false }
  ])
  
  // ❌ 错误:直接修改状态
  const toggleWrong = (id) => {
    const todo = todos.find(t => t.id === id)
    todo.completed = !todo.completed
    setTodos(todos)  // 不会触发重新渲染
  }
  
  // ✅ 正确:浅拷贝创建新数组
  const toggleRight = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    ))
  }
  
  return <div>{/* ... */}</div>
}

2. 表单数据管理

javascript
// 表单初始数据
const initialFormData = {
  user: {
    name: '',
    email: '',
    profile: {
      age: 0,
      address: ''
    }
  }
}

// ❌ 错误:直接使用会互相影响
const form1 = initialFormData
const form2 = initialFormData
form1.user.name = 'Alice'
console.log(form2.user.name)  // 'Alice'

// ✅ 正确:使用深拷贝
const form1Copy = JSON.parse(JSON.stringify(initialFormData))
const form2Copy = JSON.parse(JSON.stringify(initialFormData))
form1Copy.user.name = 'Alice'
console.log(form2Copy.user.name)  // ''

3. 数组操作

javascript
const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 }
]

// ✅ 添加新用户(不修改原数组)
const newUsers = [...users, { id: 3, name: 'Charlie', age: 35 }]

// ✅ 删除用户
const filteredUsers = users.filter(u => u.id !== 2)

// ✅ 更新用户
const updatedUsers = users.map(u =>
  u.id === 1 ? { ...u, age: 26 } : u
)

// ✅ 排序(slice 创建副本)
const sortedUsers = [...users].sort((a, b) => a.age - b.age)

4. 缓存数据

javascript
class DataCache {
  constructor() {
    this.cache = new Map()
  }
  
  // 存储:深拷贝防止外部修改
  set(key, value) {
    this.cache.set(key, structuredClone(value))
  }
  
  // 获取:深拷贝防止内部数据泄露
  get(key) {
    const value = this.cache.get(key)
    return value ? structuredClone(value) : undefined
  }
}

const cache = new DataCache()
const data = { count: 0 }
cache.set('data', data)

data.count = 10
console.log(cache.get('data').count)  // 0(缓存未受影响)

5. 配置合并

javascript
const defaultConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json'
  }
}

const userConfig = {
  timeout: 10000,
  headers: {
    'Authorization': 'Bearer token'

  // ... 中间省略 ...

  return result
}

const correctConfig = mergeConfig(defaultConfig, userConfig)
console.log(correctConfig.headers)
// { Content-Type: 'application/json', Authorization: 'Bearer token' }

6. 函数参数默认值

javascript
// ❌ 错误:使用可变对象作为默认值
function processConfig(config = { options: {} }) {
  config.options.debug = true
  return config
}

const result1 = processConfig()
const result2 = processConfig()
console.log(result1 === result2)  // true(共享引用)

// ✅ 正确:每次创建新对象
function processConfigSafe(config) {
  const defaultConfig = { options: { debug: false } }
  const merged = { ...defaultConfig, ...config }
  return merged
}

最佳实践

1. 选择合适的拷贝方式

javascript
// 简单对象,使用浅拷贝
const simpleObj = { a: 1, b: 2 }
const copy1 = { ...simpleObj }

// 复杂对象,使用深拷贝
const complexObj = { a: { b: { c: 1 } } }
const copy2 = structuredClone(complexObj)

// 包含特殊对象,使用完整深拷贝
const specialObj = { date: new Date(), func: () => {} }
const copy3 = deepClone(specialObj)

// 生产环境,使用成熟库
const productionObj = { /* ... */ }
const copy4 = _.cloneDeep(productionObj)

2. 性能优化

javascript
// ❌ 避免不必要的深拷贝
function process(obj) {
  // 如果只需要读取,不需要拷贝
  return obj.a + obj.b
}

// ✅ 只在需要修改时拷贝
function modify(obj) {
  const copy = { ...obj }
  copy.a = 10
  return copy
}

  // ... 中间省略 ...


console.log(obj.b.c)    // 2
console.log(newObj.b.c) // 20
console.log(obj !== newObj)         // true
console.log(obj.b !== newObj.b)     // true
console.log(obj.a === newObj.a)     // true(共享)

3. 不可变数据模式

javascript
// 使用 Object.freeze 防止修改
const config = Object.freeze({
  apiUrl: 'https://api.example.com',
  timeout: 5000
})

// config.timeout = 10000  // 严格模式下报错
// delete config.apiUrl    // 严格模式下报错

// 深度冻结
function deepFreeze(obj) {
  const keys = Object.keys(obj)
  keys.forEach(key => {
    if (typeof obj[key] === 'object' && obj[key] !== null) {
      deepFreeze(obj[key])
    }
  })
  return Object.freeze(obj)
}

const deepConfig = deepFreeze({
  api: { url: 'https://api.example.com' }
})

4. 拷贝性能监控

javascript
// 生产环境中监控拷贝性能
function monitoredClone(obj, name = 'object') {
  const start = performance.now()
  const clone = structuredClone(obj)
  const duration = performance.now() - start
  
  if (duration > 10) {
    console.warn(`Slow clone detected: ${name} took ${duration.toFixed(2)}ms`)
  }
  
  return clone
}

常见问题解答

Q1: 浅拷贝和深拷贝的核心区别是什么?

A: 核心区别在于嵌套对象的独立性:

  • 浅拷贝:只复制第一层属性,嵌套对象仍然共享引用
  • 深拷贝:递归复制所有层级,创建完全独立的对象
javascript
const obj = { a: 1, b: { c: 2 } }
const shallow = { ...obj }
const deep = structuredClone(obj)

shallow.b.c = 10
deep.b.c = 20

console.log(obj.b.c)  // 10(受浅拷贝影响)

Q2: 什么时候应该使用深拷贝?

A: 以下场景需要深拷贝:

  1. 需要完全独立的数据副本
  2. 嵌套层级较深的对象
  3. 防止数据污染(如缓存、历史记录)
  4. Redux/Vuex 等状态管理
javascript
// 状态管理必须使用深拷贝或不可变更新
const state = { users: [{ name: 'Alice' }] }
const newState = structuredClone(state)
newState.users[0].name = 'Bob'
console.log(state.users[0].name)  // 'Alice'(原状态未受影响)

Q3: JSON.parse(JSON.stringify()) 为什么不推荐?

A: 该方法有严重限制:

  1. 无法处理函数、undefined、Symbol
  2. Date 变成字符串,RegExp 变成空对象
  3. 无法处理循环引用
  4. 丢失不可枚举属性
javascript
const obj = {
  date: new Date(),
  func: () => {},
  undef: undefined
}

const copy = JSON.parse(JSON.stringify(obj))
// copy.date: 字符串
// copy.func: undefined(属性被删除)
// copy.undef: undefined(属性被删除)

Q4: structuredClone() 是否可以完全替代其他方法?

A: 不完全可以,有限制:

  1. 不支持函数:会抛出 DataCloneError
  2. 不支持 Error 对象
  3. 不保留原型链
  4. 浏览器兼容性:旧浏览器不支持
javascript
// ❌ 错误:包含函数
const obj = { func: () => {} }
structuredClone(obj)  // DataCloneError

// ✅ 正确:纯数据对象
const data = { users: [{ name: 'Alice' }] }
const copy = structuredClone(data)  // OK

Q5: 如何选择合适的拷贝方法?

A: 按以下流程选择:

code
开始
  ↓
需要拷贝吗?
  ├─ 否 → 直接使用引用
  └─ 是 ↓
       有嵌套对象吗?
         ├─ 否 → 浅拷贝(扩展运算符)
         └─ 是 ↓
              包含函数/特殊对象吗?
                ├─ 否 → structuredClone()
                ├─ 是 → deepClone() 或 Lodash
                └─ 需要兼容旧浏览器?
                      ├─ 是 → Lodash 或 JSON 方法
                      └─ 否 → structuredClone()

Q6: 为什么循环引用会导致 JSON.stringify 报错?

A: JSON 格式不支持循环引用,会导致无限递归:

javascript
const obj = { a: 1 }
obj.self = obj

// JSON.stringify 会无限尝试序列化
// obj -> obj.self -> obj.self.self -> ...
JSON.stringify(obj)  // TypeError: Converting circular structure to JSON

// 解决方案
// 1. 使用 structuredClone(自动处理)
const copy1 = structuredClone(obj)

// 2. 使用 WeakMap 缓存(递归实现)
const copy2 = deepClone(obj)

// 3. 移除循环引用
const safeObj = { ...obj }
delete safeObj.self

Q7: 如何实现对象的部分深拷贝?

A: 选择性深拷贝某些属性:

javascript
function partialDeepClone(obj, deepKeys = []) {
  const result = { ...obj }
  
  deepKeys.forEach(key => {
    if (result[key] !== undefined) {
      result[key] = structuredClone(result[key])
    }
  })
  
  return result
}

const obj = {
  a: 1,
  b: { c: 2 },
  d: { e: 3 }
}

// 只对 b 属性进行深拷贝
const copy = partialDeepClone(obj, ['b'])

copy.b.c = 10
copy.d.e = 20

console.log(obj.b.c)  // 10(受影响)
console.log(obj.d.e)  // 20(受影响)

Q8: 如何判断一个对象是否被正确拷贝?

A: 使用严格相等检查:

javascript
function isDeepCopied(original, copy) {
  // 基本检查
  if (original === copy) return false
  
  // 递归检查所有嵌套对象
  function checkDeep(orig, cp) {
    for (const key in orig) {
      if (typeof orig[key] === 'object' && orig[key] !== null) {
        if (orig[key] === cp[key]) return false
        if (!checkDeep(orig[key], cp[key])) return false
      }
    }
    return true
  }
  
  return checkDeep(original, copy)
}

const obj = { a: 1, b: { c: 2 } }
const shallowCopy = { ...obj }
const deepCopy = structuredClone(obj)

console.log(isDeepCopied(obj, shallowCopy))  // false
console.log(isDeepCopied(obj, deepCopy))     // true

Q9: 深拷贝会影响性能吗?

A: 会影响,优化建议:

  1. 避免不必要的拷贝:只读场景不需要拷贝
  2. 使用浅拷贝:如果嵌套对象不需要独立
  3. 使用 immer:结构共享,性能更好
  4. 延迟拷贝:只在需要时才拷贝
javascript
// ❌ 性能差:每次都深拷贝
function updateState(state, updates) {
  const newState = structuredClone(state)
  Object.assign(newState, updates)
  return newState
}

// ✅ 性能好:使用 immer
import { produce } from 'immer'

function updateStateOptimized(state, updates) {
  return produce(state, draft => {
    Object.assign(draft, updates)
  })
}

Q10: 如何在 TypeScript 中正确类型化拷贝函数?

A: 使用泛型和类型守卫:

typescript
// 深拷贝函数类型定义
function deepClone<T>(obj: T, hash = new WeakMap()): T {
  if (obj === null || typeof obj !== 'object') {
    return obj
  }
  
  if (hash.has(obj)) {
    return hash.get(obj)
  }
  
  // ... 实现代码
  return clone as T
}

// 使用示例
interface User {
  name: string
  profile: {
    age: number
  }
}

const user: User = {
  name: 'Alice',
  profile: { age: 25 }
}

const copy: User = deepClone(user)
copy.profile.age = 26
console.log(user.profile.age)  // 25(类型安全)

总结

核心要点

  1. 赋值不是拷贝:只是传递引用,修改会互相影响
  2. 浅拷贝只复制第一层:嵌套对象仍然共享引用
  3. 深拷贝完全独立:递归复制所有层级

方法选择指南

场景推荐方法原因
简单对象{...obj}简洁高效
合并对象Object.assign()支持多对象合并
数组拷贝[...arr]arr.slice()性能最优
纯数据深拷贝structuredClone()原生支持,性能好
包含函数Lodash.cloneDeep()功能全面
生产环境Lodashimmer稳定可靠

性能优先级

code
赋值 > 浅拷贝 > structuredClone > JSON 方法 > Lodash > 自定义递归

最佳实践

  1. 默认使用浅拷贝:性能更好,除非需要深拷贝
  2. 优先使用原生 APIstructuredClone() 是现代浏览器首选
  3. 生产环境用成熟库:Lodash 经过充分测试
  4. 考虑使用 immer:不可变数据更安全
  5. 监控性能:大数据拷贝可能成为瓶颈

参考资料