对象拷贝
对象拷贝是 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 | ✓ | 完整支持 |
| TypedArray | ✓ | Int8Array, 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) // true2. 特殊数值
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) // 26. 冻结对象
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: 以下场景需要深拷贝:
- 需要完全独立的数据副本
- 嵌套层级较深的对象
- 防止数据污染(如缓存、历史记录)
- 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: 该方法有严重限制:
- 无法处理函数、undefined、Symbol
- Date 变成字符串,RegExp 变成空对象
- 无法处理循环引用
- 丢失不可枚举属性
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: 不完全可以,有限制:
- 不支持函数:会抛出 DataCloneError
- 不支持 Error 对象
- 不保留原型链
- 浏览器兼容性:旧浏览器不支持
javascript
// ❌ 错误:包含函数
const obj = { func: () => {} }
structuredClone(obj) // DataCloneError
// ✅ 正确:纯数据对象
const data = { users: [{ name: 'Alice' }] }
const copy = structuredClone(data) // OKQ5: 如何选择合适的拷贝方法?
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.selfQ7: 如何实现对象的部分深拷贝?
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)) // trueQ9: 深拷贝会影响性能吗?
A: 会影响,优化建议:
- 避免不必要的拷贝:只读场景不需要拷贝
- 使用浅拷贝:如果嵌套对象不需要独立
- 使用 immer:结构共享,性能更好
- 延迟拷贝:只在需要时才拷贝
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(类型安全)总结
核心要点
- 赋值不是拷贝:只是传递引用,修改会互相影响
- 浅拷贝只复制第一层:嵌套对象仍然共享引用
- 深拷贝完全独立:递归复制所有层级
方法选择指南
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 简单对象 | {...obj} | 简洁高效 |
| 合并对象 | Object.assign() | 支持多对象合并 |
| 数组拷贝 | [...arr] 或 arr.slice() | 性能最优 |
| 纯数据深拷贝 | structuredClone() | 原生支持,性能好 |
| 包含函数 | Lodash.cloneDeep() | 功能全面 |
| 生产环境 | Lodash 或 immer | 稳定可靠 |
性能优先级
code
赋值 > 浅拷贝 > structuredClone > JSON 方法 > Lodash > 自定义递归最佳实践
- 默认使用浅拷贝:性能更好,除非需要深拷贝
- 优先使用原生 API:
structuredClone()是现代浏览器首选 - 生产环境用成熟库:Lodash 经过充分测试
- 考虑使用 immer:不可变数据更安全
- 监控性能:大数据拷贝可能成为瓶颈