You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

这个函数的逻辑是这样的:

  1. 先处理循环引用,避免递归时栈溢出
  2. 基本类型直接返回,不用折腾
  3. 函数直接返回原引用,省得克隆出没用的新函数,还能正常调用
  4. 数组会创建新数组,逐个递归克隆元素,确保嵌套数组也被深度克隆
  5. 类实例会保留原原型链,这样新实例能正常使用原类的原型方法,然后克隆所有自身属性(包括不可枚举的),保证新实例的修改不会影响原实例
  6. 普通对象同样递归克隆所有属性,彻底切断和原对象的引用

给你整个测试例子,模拟你的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 12:13:12