深拷贝,一个被低估的面试题
提示本文发布于 4 个月前,其中信息可能已经时过境迁...
以前面试被问到深拷贝,我都是直接说 JSON.parse(JSON.stringify())。后来自己认真实现了一遍,才发现这个看似简单的问题,背后涉及的知识点比想象中多得多。
先看最简单的
javascript
const cloned = JSON.parse(JSON.stringify(obj));一行代码搞定,但坑也很多:函数丢失、undefined 丢失、Symbol 丢失、Date 变字符串、循环引用直接报错。
所以面试官问深拷贝,其实不是想听你背 JSON 方法,而是想看你知不知道这些限制,以及怎么解决。
生产级深拷贝要考虑什么
1. 基本类型直接返回
javascript
if (source === null || typeof source !== "object") return source;2. 特殊内置对象
Date、RegExp、ArrayBuffer、DataView 这些不能直接复制,要分别处理:
javascript
const specialConstructors = [
[Date, src => new Date(src.getTime())],
[RegExp, src => new RegExp(src.source, src.flags)],
[ArrayBuffer, src => src.slice()],
];3. Map 和 Set
javascript
if (source instanceof Map) {
const m = new Map();
cache.set(source, m);
source.forEach((v, k) => m.set(deepClone(k, cache), deepClone(v, cache)));
return m;
}注意:Map 的键也可能是对象,所以键也要深拷贝。
4. 循环引用
这是最容易被忽略的点。如果对象里有 obj.self = obj,没有缓存机制就会死循环。
用 WeakMap 做缓存,先创建空壳再填充:
javascript
if (cache.has(source)) return cache.get(source);
const cloned = Array.isArray(source) ? [] : Object.create(Object.getPrototypeOf(source));
cache.set(source, cloned);5. 属性描述符
用 Reflect.ownKeys 获取所有自有属性(包括 Symbol 和不可枚举的),用 Object.getOwnPropertyDescriptor 获取属性描述符,区分数据属性和访问器属性:
javascript
Reflect.ownKeys(source).forEach(key => {
const desc = Object.getOwnPropertyDescriptor(source, key);
if (desc.get || desc.set) {
Object.defineProperty(cloned, key, desc);
} else {
desc.value = deepClone(source[key], cache);
Object.defineProperty(cloned, key, desc);
}
});2025 年的最佳实践
现在有了原生的 structuredClone API,浏览器和 Node.js 都支持了,性能好、功能强、循环引用安全。但函数和 Symbol 还是不支持。
所以我的建议是:现代项目优先用 structuredClone,需要兼容老环境或者需要处理函数时,回退到自定义实现。
深拷贝这个题,从"一行代码"到"生产级实现",中间隔着的就是对 JavaScript 语言本身的理解深度。
