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

React中JS类私有字段克隆失效,如何实现不可变模式?

问题:React中带私有字段的JS类无法克隆,Jest出现假阳性

我在React应用里用带私有字段的JS类(不想到处用普通Object实例),但React的不可变状态要求修改时必须克隆对象。用getter/setter访问私有字段时,发现Firefox、Chrome和Node里私有字段都没法被克隆,反而React的Jest测试环境里代码能正常跑,出现假阳性结果。如果没解决办法,只能放弃封装安全性,改用下划线前缀的伪私有字段。

我的克隆函数:

const objclone = obj => {
  const cloned = Object.assign(
    Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj),
    ),
    obj,
  );

  return cloned;
};

这个函数能克隆getter、setter和对象属性,但碰到私有字段就失效。

示例代码:

class C {
  #priv;

  constructor() {
    this.#priv = 'priv-value';
  }

  get priv() { return this.#priv; }
}

const obj = new C();
console.log("obj.priv", obj.priv);

const cloned = objclone(obj);
console.log("cloned.priv", cloned.priv);

访问cloned.priv时的错误:

  • Firefox:

Uncaught TypeError: can't access private field or method: object is not the right class

  • Chrome和Node:

Uncaught TypeError: Cannot read private member #priv from an object whose class did not declare it


原因分析

JS的私有字段(#xxx)是绑定到类本身的,不是对象实例的属性。常规克隆方法(比如Object.assign、Object.create)只能复制公开的属性和原型,根本无法访问或复制私有字段。克隆出来的对象虽然原型指向C,但没有初始化私有字段#priv,所以调用getter时会报错,因为当前对象没有该私有字段的绑定。

至于Jest里的假阳性,大概率是测试环境对私有字段的检查机制不严格,或者使用了未完全兼容私有字段的JS引擎/转译配置,导致错误没被触发。


解决办法

1. 在类内部实现自定义克隆方法

既然只有类内部能访问私有字段,就在类里加一个clone方法,手动复制私有字段的值:

class C {
  #priv;

  constructor() {
    this.#priv = 'priv-value';
  }

  get priv() { return this.#priv; }

  // 自定义克隆方法
  clone() {
    const newInstance = new C();
    // 类内部直接访问私有字段复制值
    newInstance.#priv = this.#priv;
    return newInstance;
  }
}

然后修改通用克隆函数,优先调用对象的自定义clone方法:

const objclone = obj => {
  // 如果对象有自定义克隆方法,优先调用
  if (typeof obj.clone === 'function') {
    return obj.clone();
  }
  // 原逻辑处理无自定义克隆的对象
  const cloned = Object.assign(
    Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj),
    ),
    obj,
  );
  return cloned;
};

2. 批量处理类的私有字段(如果有多个)

如果类里有多个私有字段,可以在构造函数里接收初始值,克隆时把当前实例的私有字段值传给新实例:

class C {
  #priv1;
  #priv2;

  constructor(priv1 = 'default1', priv2 = 'default2') {
    this.#priv1 = priv1;
    this.#priv2 = priv2;
  }

  get priv1() { return this.#priv1; }
  get priv2() { return this.#priv2; }

  clone() {
    // 把当前实例的私有字段值传给新实例的构造函数
    return new C(this.#priv1, this.#priv2);
  }
}

3. 避免伪私有字段(下划线前缀)

除非万不得已,不要用_priv这种伪私有字段——它本质还是公开属性,任何地方都能修改,完全失去了封装意义。优先用上面的自定义克隆方法解决问题。


内容的提问来源于stack exchange,提问作者Radu C

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:45:41