如何在JavaScript中深度克隆包含函数的对象?
如何在JavaScript中深度克隆包含函数的对象?
我完全懂你现在的头疼——要深度克隆一个嵌套了对象、数组,还混着类实例和函数的复杂结构,常用的方法要么直接报错,要么根本达不到深度克隆的效果,确实挺闹心的。
先帮你捋捋为啥那些方法都不好使:
structuredClone():这个API本来就不支持克隆函数、Symbol这类特殊类型,遇到函数直接报错太正常了- 展开运算符
{...levels[level]}和Object.assign({}, levels[level]):这俩都只能做浅拷贝,嵌套的对象和数组还是会和原数据共享引用,改了克隆后的内容,原数据也跟着变 JSON.parse(JSON.stringify()):JSON格式本身就不支持函数、undefined这些类型,用这个方法直接就把函数丢没了,完全不适合你的场景
针对你的需求——要深度克隆嵌套结构,保留函数引用,类实例能正常使用原原型方法,我给你写一个手动实现的递归克隆函数,专门处理这些情况:
function deepClone(obj, cache = new WeakMap()) { // 先处理循环引用(如果对象引用自身,避免栈溢出) if (cache.has(obj)) { return cache.get(obj); } // 基本类型和null直接返回,因为它们是值传递 if (obj === null || typeof obj !== 'object') { return obj; } // 函数不需要克隆,直接返回原引用就行(符合你"不用复制函数,能引用就好"的需求) if (typeof obj === 'function') { return obj; } let clonedObj; // 处理数组:创建新数组,递归克隆每个元素 if (Array.isArray(obj)) { clonedObj = []; cache.set(obj, clonedObj); return obj.map(item => deepClone(item, cache)); } // 处理类实例:创建继承原原型的新对象,保留原型链 const prototype = Object.getPrototypeOf(obj); clonedObj = Object.create(prototype); cache.set(obj, clonedObj); // 获取对象所有自身属性(包括不可枚举的和Symbol类型的属性) const ownKeys = [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)]; for (const key of ownKeys) { const descriptor = Object.getOwnPropertyDescriptor(obj, key); // 如果是普通属性,递归克隆值;如果是getter/setter,直接复制属性描述符 if (descriptor.value !== undefined) { clonedObj[key] = deepClone(obj[key], cache); } else { Object.defineProperty(clonedObj, key, descriptor); } } return clonedObj; }
这个函数的逻辑是这样的:
- 先处理循环引用,避免递归时栈溢出
- 基本类型直接返回,不用折腾
- 函数直接返回原引用,省得克隆出没用的新函数,还能正常调用
- 数组会创建新数组,逐个递归克隆元素,确保嵌套数组也被深度克隆
- 类实例会保留原原型链,这样新实例能正常使用原类的原型方法,然后克隆所有自身属性(包括不可枚举的),保证新实例的修改不会影响原实例
- 普通对象同样递归克隆所有属性,彻底切断和原对象的引用
给你整个测试例子,模拟你的levels场景:
// 示例类,模拟你说的类实例 class Enemy { constructor(name, health) { this.name = name; this.health = health; // 实例上的函数 this.attack = () => { console.log(`${this.name}发起攻击!`); }; } // 原型上的方法 takeDamage(damage) { this.health -= damage; console.log(`${this.name}剩余血量:${this.health}`); } } // 模拟你的levels对象 const levels = { level1: { name: "森林关卡", enemies: [new Enemy("哥布林", 50), new Enemy("野狼", 30)], // 关卡的回调函数 onEnter: () => { console.log("进入森林关卡!"); }, settings: { difficulty: "简单", timeLimit: 60 } } }; // 克隆关卡 const clonedLevel = deepClone(levels.level1); // 修改克隆后的内容 clonedLevel.enemies[0].health = 20; clonedLevel.settings.difficulty = "困难"; // 原对象不受影响 console.log(levels.level1.enemies[0].health); // 输出:50 console.log(levels.level1.settings.difficulty); // 输出:简单 // 克隆后的类实例能正常使用原型方法 clonedLevel.enemies[0].takeDamage(10); // 输出:哥布林剩余血量:10 // 函数也能正常执行 clonedLevel.onEnter(); // 输出:进入森林关卡!
如果你的对象里还有Date、Map、Set这类内置对象,还可以给这个函数加对应的处理分支,比如Date可以用new Date(obj.getTime()),Map可以遍历键值对逐个克隆,不过你没提到这些,先按你的需求来就够了。
备注:内容来源于stack exchange,提问作者Dzhake
相关产品推荐
相关产品推荐

