javascript深拷贝无法真正复制函数,只能保留原引用;需用object.prototype.tostring.call()精准判型,weakmap处理循环引用,对date/regexp/map/set等内置类型分别构造新实例。

JavaScript 中默认的深拷贝(如 JSON.parse(JSON.stringify(obj)))会丢弃函数、正则、Date、Map、Set 等非可序列化类型。若需保留函数,就不能依赖 JSON 方式,而要手写递归遍历 + 类型识别 + 特殊处理 —— 关键在于:函数本身不可“拷贝”,但可以“引用复用”或“重新构造”。实际中,所谓“支持函数类型的深拷贝”,通常指**保留原函数引用**(浅拷贝函数),而非真正复制函数逻辑(这在 JS 中不可行)。下面给出一个稳健、可扩展的实现方案。
识别并分类数据类型
深拷贝必须先准确判断值的类型,尤其区分普通对象、数组、日期、正则、Map/Set、函数等。不能只用 typeof(它对数组、null、Date 都返回 "object"),推荐结合 Object.prototype.toString.call():
-
[object Object]→ 普通对象(需递归处理) -
[object Array]→ 数组(需递归处理) -
[object Date]、[object RegExp]→ 构造新实例(new Date(date)/new RegExp(regexp)) -
[object Map]、[object Set]→ 迭代重建 -
[object Function]→ 直接返回原函数(即“保留引用”,这是唯一合理方式) -
null、基本类型(string/number/boolean/symbol/bigint)→ 直接返回
处理循环引用
对象内部可能有自引用或交叉引用(如 a.b = a),不处理会导致无限递归栈溢出。解决方案是维护一个 WeakMap 缓存已拷贝过的源对象及其对应拷贝结果:
- 每次进入递归前,先查 WeakMap 是否已有该源对象的拷贝
- 若有,直接返回缓存结果(打破循环)
- 若无,先新建目标对象({} 或 []),存入 WeakMap,再递归填充属性
函数的处理原则与注意事项
JS 中函数是第一类对象,但无法真正“复制”其闭包、内部状态或字节码。所谓“支持函数”,实质是:不跳过、不报错、不转成 undefined,而是原样保留引用。需注意:
- 箭头函数、普通函数、class 方法、bind 后的函数,均属于
[object Function],统一原样返回 - 不要尝试
eval(func.toString())—— 会丢失闭包、this 绑定、不可枚举属性,且存在安全与兼容性风险 - 若需“隔离”函数行为(如沙箱场景),应换用其他架构(如 Web Worker、Proxy 拦截),而非深拷贝
完整可运行示例
以下是一个轻量、带循环引用防护、明确支持函数保留的深拷贝函数:
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (typeof obj === 'function') return obj; // ✅ 函数直接返回原引用
if (seen.has(obj)) return seen.get(obj);
let cloned;
const Ctor = obj.constructor;
// 处理内置对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
cloned = new Map();
obj.forEach((v, k) => cloned.set(k, deepClone(v, seen)));
return cloned;
}
if (obj instanceof Set) {
cloned = new Set();
obj.forEach(v => cloned.add(deepClone(v, seen)));
return cloned;
}
// 普通对象或数组
cloned = Array.isArray(obj) ? [] : {};
seen.set(obj, cloned);
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key], seen);
}
}
return cloned;
}
使用示例:
const original = {
name: 'test',
fn: function() { return 42; },
arr: [1, { x: this.fn }],
self: null
};
original.self = original; // 循环引用
const copy = deepClone(original);
console.log(copy.fn === original.fn); // true ✅ 函数引用未变
console.log(copy.self === copy); // true ✅ 循环引用被正确重建
不复杂但容易忽略细节。核心就三点:类型精准识别、WeakMap 拦截循环、函数不做转换只保留引用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











