Skip to content

深拷贝,一个被低估的面试题

提示本文发布于 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 语言本身的理解深度。