要实现“元数据保护”的拷贝,需先用Object.assign()复制可枚举自有属性值,再用Object.defineProperties()结合Object.getOwnPropertyDescriptors()补全不可枚举属性、getter/setter及属性描述符等元数据。

要实现具备“元数据保护”的拷贝,核心思路是:用 Object.assign() 快速复制可枚举自有属性的值,再用 Object.defineProperties() 补回那些被 assign 忽略的关键元数据——包括不可枚举属性、getter/setter、writable、configurable、enumerable 等特性。
为什么 assign 无法保护元数据?
Object.assign() 只复制**可枚举的自有属性值**,它:
- 忽略不可枚举属性(如
Object.prototype.toString或手动设为enumerable: false的属性) - 不保留 getter/setter,而是直接读取值并写死为普通数据属性
- 重置所有属性描述符为默认值:
writable: true、configurable: true、enumerable: true - 跳过继承属性和 Symbol 键(除非显式提取)
defineProperties 如何补全元数据?
Object.defineProperties(target, descriptors) 允许你精确控制每个属性的行为。关键在于:先用 Object.getOwnPropertyDescriptors() 提取源对象所有自有属性的完整描述符(含 getter/setter 和各布尔标志),再将这些描述符应用到目标对象上。
注意:该方法只处理**自有属性**,不递归处理嵌套对象——元数据保护仅作用于顶层对象本身。
组合使用的安全拷贝函数
下面是一个兼顾值拷贝与元数据保留的浅拷贝实现(不深克隆):
function copyWithMetadataProtection(source) {
if (source == null || typeof source !== 'object') return source;
const target = Object.assign({}, source); // 复制可枚举值
const descriptors = Object.getOwnPropertyDescriptors(source);
// 覆盖 target 上的属性定义,还原元数据
Object.defineProperties(target, descriptors);
return target;
}
示例:
const src = {};
Object.defineProperty(src, 'id', {
value: 42,
writable: false,
enumerable: false,
configurable: false
});
Object.defineProperty(src, 'name', {
get() { return 'Alice'; },
enumerable: true
});
const copy = copyWithMetadataProtection(src);
console.log(copy.id); // 42(但 copy.id 不可写)
console.log(copy.name); // 'Alice'(触发 getter)
console.log(propertyIsEnumerable.call(copy, 'id')); // false ✅
console.log(Object.getOwnPropertyDescriptor(copy, 'id').writable); // false ✅
注意事项与局限
这个方案是浅层元数据保护,适用于需要精确复现对象行为的场景(如配置对象、代理模板、DSL 实体)。但它有明确边界:
- 不处理原型链——
copy.__proto__默认是Object.prototype,若需继承原原型,需额外设置Object.setPrototypeOf(copy, Object.getPrototypeOf(source)) - 不深拷贝嵌套对象——内部引用仍共享,元数据只保留在顶层
- Symbol 属性会被
getOwnPropertyDescriptors捕获,因此也受保护;但assign默认不复制 Symbol 键,所以函数中应改用Object.assign(target, source)→ 实际已支持 Symbol(ES2015+),但为保险可显式合并:Object.assign(target, source, Object.getOwnPropertyDescriptors(source))不适用,应保持两步分离 - 不可扩展性(
Object.preventExtensions)、密封(seal)、冻结(freeze)状态不会被自动继承,需单独检查并调用对应方法

















