toJS() 是对象自描述的结构化导出协议,返回 plain object 供构造函数重建实例;它不侵入原型、明确拷贝边界、纯函数无副作用,与 clone() 职责分离,协同实现可控安全的拷贝逻辑。

直接在对象内部定义可预测、可复用的拷贝逻辑,比依赖外部通用深克隆工具更可控、更安全。关键不是“怎么克隆”,而是“对象自己清楚该怎么被复制”——toJS() 就是这样一种轻量、明确、面向语义的自描述协议。
toJS() 的核心定位:结构化导出,而非直接克隆
它不负责创建新实例,只负责把当前对象的可序列化状态以 plain object 形式返回。这个对象天然适配构造函数、Object.assign 或 JSON 流程,是克隆链中承上启下的中间表示。
- 避免侵入性:不修改原型链,不依赖反射或私有字段访问(如 #name),符合封装原则
- 明确边界:哪些字段参与拷贝,由业务决定(比如忽略缓存、临时计算值、DOM 引用)
- 天然兼容深拷贝:返回 plain object 后,可自由选择 Object.assign、structuredClone 或手动重建
配合类构造器完成完整克隆
toJS() 需与类的构造逻辑协同工作。理想情况下,构造函数应能接受 toJS() 输出的对象作为参数。
- 基础写法:
new Person(obj)直接接收 plain object 参数,内部解构赋值 - 增强写法:构造函数重载,区分 (name, age) 和 (plainObj),保持向后兼容
- 示例:const clone = new Person(original.toJS()); —— 简洁、类型安全、无副作用
处理私有字段与访问器的典型模式
当对象含私有字段(#name)或公有 getter/setter(背后操作私有状态)时,toJS() 是最自然的出口。
- 私有字段:在 toJS() 中显式读取并返回,如
return { name: this.#name, age: this.#age }; - getter 只读字段:直接在 toJS() 中调用,如
fullName: this.fullName - 计算属性/派生字段:按需决定是否包含(通常不包含,避免冗余或不一致)
- 避免副作用:toJS() 应为纯函数,不触发 setter、不修改内部状态、不发起网络请求
与 clone() 方法共存,职责分离
toJS() 不替代 clone(),而是为其提供底层数据支撑。二者可分层协作:
-
clone():对外统一入口,语义清晰,使用者无需关心实现细节 -
toJS():对内数据契约,供 clone() 调用,也可被测试、日志、持久化等其他场景复用 - 典型实现:
clone() { return new this.constructor(this.toJS()); }


















