Object.getOwnPropertyDescriptors无法获取私有字段和私有访问器,因其运行时不可枚举、不可反射、不可遍历;可靠克隆需类自身提供clone()等方法或序列化接口。
Object.getOwnPropertyDescriptors 本身无法获取或克隆私有字段(#field)和私有访问器(get #prop() / set #prop(v)),因为它们在 JavaScript 中是**运行时不可枚举、不可反射、不可遍历**的——`Object.getOwnPropertyDescriptors`、`Reflect.ownKeys`、`for...in`、`Object.keys()` 等全部对其完全透明,就像它们不存在一样。
私有名称(#xxx)的作用域严格绑定到类定义内部,不参与常规属性反射机制。V8、SpiderMonkey 等引擎明确将私有字段/访问器排除在所有元属性 API 之外。这不是限制,而是语言设计原则:私有即“不可观测”。因此:
Object.getOwnPropertyDescriptors(instance) 返回空对象或仅含公有属性描述符Object.getOwnPropertyNames(instance) 不返回任何 #xxx
真正可靠的克隆必须由类主动支持,常见模式有:
clone() 实例方法:在类内部手动复制私有状态(需访问私有字段)toJSON() 或 toPlainObject(),返回可安全克隆的数据快照例如:
class Person { #name; #age; constructor(name, age) { this.#name = name; this.#age = age; } clone() { // ✅ 可直接访问私有字段,安全重建 return new Person(this.#name, this.#age); } toJSON() { // ✅ 提供结构化数据,供外部深克隆 return { name: this.#name, age: this.#age }; }}const a = new Person("Alice", 30);const b = a.clone(); // 完整克隆,含私有状态const c = Object.assign(new Person(), a.toJSON()); // 需确保构造函数接受 plain object
若你有公有 getter/setter 背后操作私有字段(典型封装模式),可借助 getOwnPropertyDescriptors 克隆公有部分,并配合类内逻辑补全私有状态:
Object.getOwnPropertyDescriptors(obj) 获取所有公有属性(含访问器)的完整描述符Object.defineProperties({}, descriptors) 创建新对象骨架clone() 或构造参数注入 —— 描述符 API 无法帮你“写入”#xxx
示例:
class Counter { #value = 0; get count() { return this.#value; } set count(v) { this.#value = Math.max(0, v); } clone() { const copy = new Counter(); copy.#value = this.#value; // ✅ 唯一合法写入方式 return copy; }}const c1 = new Counter();c1.count = 42;const c2 = c1.clone(); // 正确克隆私有 #value
JavaScript 的私有特性不是障碍,而是提示:克隆逻辑应属于类契约的一部分。强行用反射“破解”私有字段违背封装初衷,也注定失败。真正健壮的做法是:
clone()、copy() 或 from() 静态方法structuredClone()(对可克隆值)或 JSON.stringify/parse(对纯数据)辅助公有状态