直接克隆含私有属性(#field)的es6类实例会失败,因私有字段无法被外部读取或遍历;应通过类内显式定义clone()方法,利用this.constructor和内部可访问性实现语义等价的新实例。

直接克隆含私有属性(#field)的 ES6 类实例会失败,因为私有字段无法被外部读取或遍历,JSON.stringify、Object.assign、扩展运算符({...obj})等通用克隆方式都会跳过或报错。关键不是“怎么复制”,而是“如何在保留封装前提下实现语义等价的新实例”。
明确私有属性不可反射访问
ES2022+ 的 # 私有字段由语言强制保护:它不出现在 Object.keys()、Reflect.ownKeys()、for...in 中,也不能通过 this.#field 以外的任何方式读取——包括在克隆逻辑中尝试访问。试图用 Object.getOwnPropertyDescriptors(this) 获取私有字段描述符会直接忽略它们。
推荐做法:提供显式的克隆方法
在类内部定义一个公开的 clone() 方法,由类自己控制如何初始化新实例的私有状态:
- 构造函数接受可选参数或配置对象,用于还原私有字段值
-
clone()方法调用new this.constructor(...)并传入当前私有值(类内可合法访问this.#x) - 避免暴露私有字段,也不依赖外部工具解析内部结构
示例:
class Counter {#value = 0;
constructor(value = 0) { this.#value = value; }
get value() { return this.#value; }
clone() { return new Counter(this.#value); }
}
不建议的替代方案
以下方式看似可行,但存在明显缺陷:
-
用 WeakMap 或 Symbol 模拟私有:虽可被外部遍历或序列化,但已失去真正私有语义,违背使用
#的初衷 -
手动列出所有私有字段并逐个赋值:破坏封装、易遗漏、无法应对动态私有字段(如未来新增
#id) -
用
structuredClone():目前不支持私有字段,会丢失它们(Chrome/Firefox 中克隆后新实例的#value为初始值或undefined)
子类继承时的克隆注意事项
若类被继承,clone() 方法需考虑子类构造签名:
- 父类
clone()应调用new this.constructor(...)(而非硬编码new Parent(...)),确保返回正确子类实例 - 子类需确保其构造函数能接收父类传递的参数,并正确初始化自己的私有字段
- 避免在
clone()中调用可能被子类覆盖的非私有方法,以防意外副作用











