通过属性描述符扫描实现原始对象“只读”特性的深度迁移,核心是逐层提取并复刻每个属性的writable和configurable等元信息。1. 用Object.getOwnPropertyDescriptors获取全量描述符;2. 递归处理嵌套对象与数组,跳过函数等原始类型;3. 用Object.defineProperties批量注入目标对象;4. 注意严格模式、PHP readonly及Proxy等边界适配。

要通过属性描述符扫描实现原始对象“只读”特性的深度迁移,核心在于:**逐层提取原对象每个属性的描述符(尤其是 writable 和 configurable 状态),再将这些元信息精准复刻到新对象对应属性上**。这不是简单复制值,而是复制“不可变性契约”本身。
1. 扫描原始对象的完整属性描述符
使用 Object.getOwnPropertyDescriptors() 获取对象自身所有属性的完整描述符对象,它比 getOwnPropertyDescriptor 更适合深度处理:
- 该方法返回一个键为属性名、值为描述符对象的映射,覆盖数据属性和访问器属性
- 能捕获
writable: false、configurable: false、enumerable: false等关键只读信号 - 对嵌套对象需递归调用,不能只处理顶层
2. 递归识别并保留只读语义层级
仅设置顶层 writable: false 不足以实现“深度只读”,必须向下穿透:
- 对每个属性值,若为对象且非
null,递归调用同一扫描逻辑获取其描述符 - 若某属性值是数组,需单独处理:数组长度和索引项都可能被冻结(如
Object.freeze([])) - 跳过函数、正则、日期等原始包装类型——它们本身不具“可写属性”,但需保留其不可枚举性
3. 构建目标对象并复刻描述符
用 Object.defineProperties() 将扫描结果一次性注入新对象,避免逐个 defineProperty 的性能损耗:
- 对每个属性,直接把扫描得到的描述符对象传入,无需手动拼接
{ writable, enumerable, configurable, value } - 若原属性是 getter/setter,描述符中会含
get和set字段,应原样复刻,确保访问逻辑不变 - 特别注意
configurable: false—— 它阻止后续对描述符本身的修改,是只读锁定的关键一环
4. 处理边界情况与语言差异
不同环境对只读的实现有细微差别,需针对性适配:
- 在严格模式下,尝试给
writable: false属性赋值会抛TypeError;非严格模式静默失败,迁移后建议启用严格模式验证 - PHP 8.3 的
readonly属性不暴露为 JS 可见的描述符,若对接 PHP 对象需通过反射 API 或序列化协议协商只读语义 - Proxy 对象的只读行为无法被
getOwnPropertyDescriptors完整捕获,需额外检查handler.set是否抛错










