深拷贝
题目描述
实现一个完整的深拷贝函数,要求:
- 支持对象和数组的深层复制
- 解决循环引用问题
- 处理各种数据类型(Date、RegExp、Symbol 等)
- 保持原对象的所有特性和属性
本题考查 递归算法、数据类型判断 和 循环引用处理 的综合能力。
核心知识点
1. 深拷贝 vs 浅拷贝
- 浅拷贝: 只复制对象的第一层属性,深层属性仍共享引用
- 深拷贝: 递归复制所有层级的属性,创建完全独立的副本
- 应用场景: 防止意外修改、状态管理、数据备份
2. 循环引用问题
javascript
const a = { name: 'Alice' }
a.self = a // 创建循环引用
// 不处理循环引用会导致:
// RangeError: Maximum call stack size exceeded3. JavaScript 数据类型分类
- 基本类型: number, string, boolean, undefined, null, symbol, bigint
- 引用类型: Object, Array, Date, RegExp, Function, Map, Set 等
- 特殊处理: Symbol 键、不可枚举属性、getter/setter
代码实现
javascript
export default function deepClone(obj, cache = new Map()) {
// 处理循环引用:如果已经克隆过,直接返回
if (cache.has(obj)) {
return cache.get(obj)
}
let result = null
if (Array.isArray(obj)) {
result = handleArray(obj, cache)
}
else if (isObject(obj)) {
result = handleObject(obj, cache)
}
else {
result = handleBasic(obj)
}
return result
}
// 处理数组
function handleArray(arr, cache) {
const tmp = []
// 先将结果存入缓存,防止循环引用
cache.set(arr, tmp)
arr.forEach((el, idx) => {
tmp[idx] = deepClone(el, cache)
})
return tmp
}
// 处理对象
function handleObject(obj, cache) {
// 处理特殊对象类型
if (obj instanceof Date) {
return new Date(obj.getTime())
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags)
}
if (obj instanceof Error) {
const error = new Error(obj.message)
error.name = obj.name
error.stack = obj.stack
return error
}
// 处理 Map
if (obj instanceof Map) {
const map = new Map()
cache.set(obj, map)
for (const [key, value] of obj) {
map.set(deepClone(key, cache), deepClone(value, cache))
}
return map
}
// 处理 Set
if (obj instanceof Set) {
const set = new Set()
cache.set(obj, set)
for (const value of obj) {
set.add(deepClone(value, cache))
}
return set
}
// 处理普通对象
const tmp = {}
cache.set(obj, tmp) // 先存入缓存防止循环引用
// 复制所有可枚举属性(包括 Symbol 键)
const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]
keys.forEach((key) => {
tmp[key] = deepClone(obj[key], cache)
})
return tmp
}
// 处理基本类型
function handleBasic(obj) {
return obj // 基本类型直接返回
}
// 判断是否为对象
function isObject(obj) {
return obj !== null && typeof obj === 'object'
}关键技术点
1. 循环引用检测与处理
javascript
// 使用 Map 缓存已克隆的对象
const cache = new Map();
// 检测循环引用
if (cache.has(obj)) {
return cache.get(obj); // 返回已克隆的对象
}
// 先存入缓存,再进行递归克隆
cache.set(obj, result);2. 数据类型精确判断
javascript
// 数组判断
Array.isArray(obj)
// 对象类型判断
obj instanceof Date
obj instanceof RegExp
obj instanceof Error
// 基本类型判断
typeof obj === 'object' && obj !== null3. Symbol 属性处理
javascript
// 获取包括 Symbol 在内的所有属性
const keys = [
...Object.keys(obj), // 字符串键
...Object.getOwnPropertySymbols(obj), // Symbol 键
]4. 特殊对象的克隆技巧
javascript
// Date 对象
new Date(obj.getTime())
// RegExp 对象
new RegExp(obj.source, obj.flags)
// Error 对象
const error = new Error(obj.message)
error.name = obj.name
error.stack = obj.stack5. 常见陷阱和坑点
- 忘记处理循环引用: 导致栈溢出
- 遗漏 Symbol 属性: Symbol 键被忽略
- 特殊对象处理不当: Date、RegExp 等失去特性
- 原型链问题: 只克隆自有属性,不包括原型
- 不可枚举属性: Object.keys() 无法获取
使用示例
javascript
// 基本对象拷贝
const original = {
name: 'John',
age: 30,
hobbies: ['reading', 'coding'],
address: {
city: 'New York',
country: 'USA',
},
}
const cloned = deepClone(original)
cloned.address.city = 'Boston'
console.log(original.address.city) // 'New York' (未受影响)
// 循环引用处理
const obj = { name: 'Alice' }
obj.self = obj
obj.friend = { name: 'Bob', bestFriend: obj }
const clonedObj = deepClone(obj)
console.log(clonedObj.self === clonedObj) // true
console.log(clonedObj.friend.bestFriend === clonedObj) // true
// 特殊类型处理
const complex = {
date: new Date('2023-01-01'),
regex: /hello/gi,
error: new Error('test error'),
map: new Map([
['key1', 'value1'],
['key2', { nested: true }],
]),
set: new Set([1, 2, 3, { a: 1 }]),
symbol: Symbol('test'),
[Symbol('key')]: 'symbol value',
}
const clonedComplex = deepClone(complex)
console.log(clonedComplex.date instanceof Date) // true
console.log(clonedComplex.regex instanceof RegExp) // true
console.log(clonedComplex.map instanceof Map) // true
// 数组深拷贝
const nestedArray = [
1,
'string',
[2, 3, [4, 5]],
{ a: 1, b: [6, 7] },
new Date(),
]
const clonedArray = deepClone(nestedArray)
clonedArray[2][2][0] = 999
console.log(nestedArray[2][2][0]) // 4 (未受影响)
// 函数处理(通常不克隆,保持引用)
const withFunction = {
data: [1, 2, 3],
method() {
return this.data.length
},
}
const clonedWithFunction = deepClone(withFunction)
console.log(clonedWithFunction.method === withFunction.method) // true记忆要点
核心记忆点
- 循环引用检测 - 使用 Map 缓存已克隆对象
- 类型精确判断 - instanceof 和 typeof 结合使用
- Symbol 属性 - 使用 getOwnPropertySymbols 获取
- 特殊对象处理 - Date、RegExp、Error 等需要特殊构造
- 递归克隆 - 深度优先遍历所有属性
处理顺序记忆
javascript
// 1. 检查缓存(循环引用)
// 2. 判断类型(数组、对象、基本类型)
// 3. 创建新容器并缓存
// 4. 递归克隆所有属性
// 5. 返回克隆结果扩展思考
1. 性能优化版本
javascript
function deepCloneOptimized(obj, cache = new WeakMap()) {
// 使用 WeakMap 优化内存使用
if (cache.has(obj)) {
return cache.get(obj)
}
// 快速路径:基本类型直接返回
if (obj === null || typeof obj !== 'object') {
return obj
}
// 构造函数映射,避免重复 instanceof 检查
const ctorMap = new Map([
[Date, o => new Date(o.getTime())],
[RegExp, o => new RegExp(o.source, o.flags)],
[Array, o => []],
[Object, (o) => {}],
])
const ctor = obj.constructor
const cloner = ctorMap.get(ctor)
if (!cloner) {
return obj // 不支持的类型直接返回
}
const result = cloner(obj)
cache.set(obj, result)
if (ctor === Array || ctor === Object) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCloneOptimized(obj[key], cache)
}
}
}
return result
}2. 支持自定义克隆策略
javascript
function deepCloneWithStrategy(obj, options = {}) {
const {
includeNonEnumerable = false,
includeSymbols = true,
cloneFunction = false,
maxDepth = Infinity,
currentDepth = 0,
} = options
// 深度限制
if (currentDepth >= maxDepth) {
return obj
}
const cache = options.cache || new WeakMap()
if (cache.has(obj)) {
return cache.get(obj)
}
// 函数处理策略
if (typeof obj === 'function') {
if (cloneFunction) {
// 简单的函数克隆(有限制)
return new Function(`return ${obj.toString()}`)()
}
return obj
}
// 获取属性的策略
let keys = Object.keys(obj)
if (includeSymbols) {
keys = keys.concat(Object.getOwnPropertySymbols(obj))
}
if (includeNonEnumerable) {
const allKeys = Object.getOwnPropertyNames(obj)
keys = keys.concat(allKeys.filter(k => !keys.includes(k)))
}
const result = Array.isArray(obj) ? [] : {}
cache.set(obj, result)
keys.forEach((key) => {
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
if (descriptor.get || descriptor.set) {
// 处理 getter/setter
Object.defineProperty(result, key, {
get: descriptor.get,
set: descriptor.set,
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
})
}
else {
result[key] = deepCloneWithStrategy(obj[key], {
...options,
currentDepth: currentDepth + 1,
cache,
})
}
})
return result
}3. 流式深拷贝(处理大对象)
javascript
async function streamDeepClone(obj, options = {}) {
const { chunkSize = 1000, delay = 0 } = options
const cache = new WeakMap()
async function cloneChunk(obj, processedCount = 0) {
if (cache.has(obj)) {
return cache.get(obj)
}
if (typeof obj !== 'object' || obj === null) {
return obj
}
const result = Array.isArray(obj) ? [] : {}
cache.set(obj, result)
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
result[key] = await cloneChunk(obj[key], processedCount + 1)
// 定期让出控制权
if (processedCount % chunkSize === 0 && delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay))
}
}
return result
}
return await cloneChunk(obj)
}4. 类型安全的深拷贝
javascript
function typedDeepClone(obj, typeGuards = {}) {
const cache = new WeakMap()
function clone(value, expectedType) {
if (cache.has(value)) {
return cache.get(value)
}
// 类型检查
if (expectedType && typeGuards[expectedType]) {
if (!typeGuards[expectedType](value)) {
throw new TypeError(`Expected ${expectedType}, got ${typeof value}`)
}
}
if (typeof value !== 'object' || value === null) {
return value
}
const result = Array.isArray(value) ? [] : {}
cache.set(value, result)
for (const key in value) {
if (value.hasOwnProperty(key)) {
result[key] = clone(value[key])
}
}
return result
}
return clone(obj)
}
// 使用示例
const typeGuards = {
user: obj =>
obj && typeof obj.name === 'string' && typeof obj.age === 'number',
product: obj =>
obj && typeof obj.id === 'number' && typeof obj.price === 'number',
}
const user = { name: 'John', age: 30, address: { city: 'NYC' } }
const clonedUser = typedDeepClone(user, typeGuards)5. 支持原型链的深拷贝
javascript
function deepCloneWithPrototype(obj, cache = new WeakMap()) {
if (cache.has(obj)) {
return cache.get(obj)
}
if (typeof obj !== 'object' || obj === null) {
return obj
}
// 保持原型链
const proto = Object.getPrototypeOf(obj)
const result = Object.create(proto)
cache.set(obj, result)
// 复制所有自有属性(包括不可枚举的)
const propNames = Object.getOwnPropertyNames(obj)
const propSymbols = Object.getOwnPropertySymbols(obj)
[...propNames, ...propSymbols].forEach(key => {
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
if (descriptor.value !== undefined) {
descriptor.value = deepCloneWithPrototype(descriptor.value, cache)
}
Object.defineProperty(result, key, descriptor)
})
return result
}