简体   繁体   English

JavaScript:有没有办法深度复制对象的不可写属性?

[英]JavaScript: Is there a way to deep copy an object's non-writable property?

Let's say we have an object obj with its foo property being non-writable假设我们有一个对象obj ,它的foo属性是不可写的


const obj = {foo: {}}
Object.defineProperty(obj, 'foo', {value: {bar: 'baz'}, writable: false, enumerable: true})

And I want to deep copy this object with its original property descriptors preserved.我想深度复制这个对象并保留其原始属性描述符。 So for the copied object, its foo property should still be non-writable.所以对于复制的对象,它的foo属性应该仍然是不可写的。


const propertyDescriptors = Object.getOwnPropertyDescriptors(obj)
const cloned = Object.create(
  Object.getPrototypeOf(obj),
  propertyDescriptors
)

But the problem occurs when I want to deep copy the foo property's value.但是当我想深度复制foo属性的值时,就会出现问题。 Note that I wrote a recursive algorithm to deep copy but I just used the spread operator here for brevity.请注意,我为深度复制编写了一个递归算法,但为了简洁起见,我只是在这里使用了扩展运算符。

cloned.foo = {...obj.foo}

Now there is an error because cloned.foo has writable: false because we preserved the property descriptor from the original obj现在有一个错误,因为cloned.foowritable: false因为我们保留了原始obj的属性描述符

I am thinking if there is a way to get around this so that I can deep copy the value of the property and also preserve its original property descriptors?我在想是否有办法解决这个问题,以便我可以深度复制属性的值并保留其原始属性描述符?

Updated @2022-05-31:更新@2022-05-31:

The below code go through every property deepCopyWithPropertyDescriptors and copy the value and descriptors with dynamic programming .下面的代码遍历每个属性deepCopyWithPropertyDescriptors并使用dynamic programming复制值和描述符。 Be aware of the max depth of object to avoid stack overflow.请注意对象的最大深度以避免堆栈溢出。

Run the code to see the result before and after clone compared side by side.运行代码并排比较克隆前后的结果。

 function isObject(value) { var type = typeof value return value != null && (type == 'object' || type == 'function') } function deepCopyWithPropertyDescriptors(o) { const resultObj = {} const desc = Object.getOwnPropertyDescriptors(o) delete desc.value for(const key in o) { const value = o[key] if(isObject(value)) { resultObj[key] = deepCopyWithPropertyDescriptors(value) } else { resultObj[key] = value } } Object.defineProperties(resultObj, desc) return resultObj } // Examples 1 const obj = {foo: {}} Object.defineProperty(obj, 'foo', {value: {bar: 'baz'}, writable: false, enumerable: true}) const cloned = deepCopyWithPropertyDescriptors(obj) console.log("obj", Object.getOwnPropertyDescriptors(obj), Object.getOwnPropertyDescriptors(cloned)) // Examples 2 const obj2 = {foo: {}} const obj2xfoo = {bar: 'xx'} Object.defineProperty(obj2xfoo, 'bar', {value: 'baz', writable: true, enumerable: true, configurable: false}) Object.defineProperty(obj2, 'foo', {value: obj2xfoo, writable: false, enumerable: true, configurable: false}) const cloned2 = deepCopyWithPropertyDescriptors(obj2) console.log("obj2.foo:", Object.getOwnPropertyDescriptors(obj2.foo), Object.getOwnPropertyDescriptors(cloned2.foo)) console.log("obj2:", Object.getOwnPropertyDescriptors(obj2), Object.getOwnPropertyDescriptors(cloned2))

The following won't copy property descriptors:以下不会复制属性描述符:

structuredClone

You can use the latest deep clone browser api structuredClone , as you do not care the compatibilities of these browsers: Internet Explorer, Opera Android, Samsung Internet.您可以使用最新的深度克隆浏览器structuredClone ,因为您不关心这些浏览器的兼容性:Internet Explorer、Opera Android、Samsung Internet。

 const obj = {foo: {}} Object.defineProperty(obj, 'foo', {value: {bar: 'baz'}, writable: false, enumerable: true}) const propertyDescriptors = Object.getOwnPropertyDescriptors(obj) const cloned0 = structuredClone(obj) console.log(cloned0)

JSON.parse and JSON.stringify JSON.parseJSON.stringify

You can use the JSON.parse and JSON.stringify functions to bypass the writable read only check.您可以使用JSON.parseJSON.stringify函数绕过writable只读检查。

 const obj = {foo: {}} Object.defineProperty(obj, 'foo', {value: {bar: 'baz'}, writable: false, enumerable: true}) const propertyDescriptors = Object.getOwnPropertyDescriptors(obj) const cloned1 = JSON.parse(JSON.stringify(obj)) console.log(cloned1)

Reference参考

You can use Object.defineProperty() a couple of times will give the expected result.您可以多次使用Object.defineProperty()将给出预期的结果。

With first defineProperty() call you can create property with value undefined and property descriptor configurable: true .通过第一次defineProperty()调用,您可以创建值为undefined且属性描述符configurable: true The next call sets the value to the added property and then updates the property descriptor as per the original object into a cloned object.下一次调用将值设置为添加的属性,然后根据原始对象将属性描述符更新为克隆对象。 Calling defineProperty() with the same key, allows modifying value and descriptor if configurable is true.如果可配置为真,则使用相同的键调用defineProperty()允许修改值和描述符。

 'use strict'; const obj = {foo: {}} const foo = {bar: 'baz'}; Object.defineProperty(obj, 'foo', {value: {bar: 'baz'}, writable: false, enumerable: true}) const clonedObj = cloneObject(obj); //comparing with original object property console.log(obj.foo === clonedObj.foo); //comparing with different object with same properties console.log(foo === clonedObj.foo); console.log('\nOriginal Object'); objWithDiscriptors(obj); console.log('\nClonned Object'); objWithDiscriptors(clonedObj); function cloneObject(sourceObj){ const __clone = {}; Object.keys(sourceObj).map(k => { const propertyDescriptor = Object.getOwnPropertyDescriptor(sourceObj, k); Object.defineProperty(__clone, k, {configurable: true}); if(typeof sourceObj[k] === 'object' && sourceObj[k] !== null && sourceObj[k] !== undefined && !Array.isArray(sourceObj[k])){ Object.defineProperty(__clone, k, {value: cloneObject(sourceObj[k])}); }else{ Object.defineProperty(__clone, k, {value: sourceObj[k]}); } Object.defineProperty(__clone, k, { writable: propertyDescriptor.writable, enumerable: propertyDescriptor.enumerable, configurable: propertyDescriptor.configurable }); }) return __clone; } function objWithDiscriptors(obj){ Object.keys(obj).map(k => { console.group(k); const descriptor = Object.getOwnPropertyDescriptor(obj, k); console.log(`configurable: ${descriptor.configurable}`); console.log(`enumerable: ${descriptor.enumerable}`); console.log(`writable: ${descriptor.writable}`); if(typeof obj[k] == 'object' && obj[k] !== null && obj[k] !== undefined){ objWithDiscriptors(obj[k]); } console.groupEnd() }) }

Additional check and logic can be added for array of objects.可以为对象数组添加额外的检查和逻辑。

If I understand your requirement correctly, You want to deep copy the source object into a target object by preserving source object properties ( writable , enumerable ).如果我正确理解您的要求,您希望通过保留源对象属性( writableenumerable )将源对象深度复制到目标对象中。 If Yes, You can use Object.assign() which copies all enumerable own properties from source object to a target object and returns the modified target object.如果是,您可以使用Object.assign()将所有可枚举的自身属性从源对象复制到目标对象并返回修改后的目标对象。

 const obj = {}; Object.defineProperty(obj, 'foo', { value: {bar: 'baz'}, writable: false, enumerable: true }); const objDescriptor = Object.getOwnPropertyDescriptor(obj, 'foo'); const structuredObj = Object.assign(obj); const structuredObjDescriptor = Object.getOwnPropertyDescriptor(structuredObj, 'foo'); console.log(objDescriptor); console.log(structuredObjDescriptor);

One limitation : Above solution is performing shallow copy not a deep copy .一个限制上述解决方案是执行shallow copy而不是deep copy

Just curious, As writable property is false and user can not modify the foo property value, Then why we need to perform deep copy of the source object ?只是好奇,由于writable属性是false的,用户不能修改foo属性值,那么为什么我们需要对源对象执行深拷贝呢?

Update :更新 :

To get both enumerable as well as non-enumerable properties from the cloned object.从克隆对象中获取可枚举和不可枚举的属性。 You can use Object.getOwnPropertyNames() method and then iterate over the object keys.您可以使用Object.getOwnPropertyNames()方法,然后遍历对象键。

Demo :演示

 const obj = {}; Object.defineProperty(obj, 'foo', { value: {bar: 'baz'}, writable: false, enumerable: false }); const structuredObj = Object.assign(obj); console.log(JSON.stringify(structuredObj)); // An empty object as property is non enumerable. // This iteration will return both enumerable as well as non-enumerable properties. Object.getOwnPropertyNames(structuredObj).forEach(function(property) { console.log(property, structuredObj[property]); });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在 Javascript 中修改不可配置、不可写的属性? - How to modify non-configurable, non-writable properties in Javascript? 不可写字符问题 - Non-writable character issue 在没有Object.defineProperty的情况下定义不可写属性? - Defining non-writable properties without Object.defineProperty? 导入的对象是不可写的吗? - Are imported objects non-writable? 使现有的不可写和不可配置属性可写和可配置 - Make existing non-writable and non-configurable property writable and configurable 尝试对 function 进行存根导致 Descriptor for property is non-configurable and non-writable - Trying to stub a function results in Descriptor for property is non-configurable and non-writable JS导入是不可变的还是不可写的? - Are JS imports immutable or non-writable? TypeError:无法在具有不可写长度的数组末尾定义数组索引属性 - TypeError: can't define array index property past the end of an array with non-writable length 拼接方法出错:无法在具有不可写长度的数组末尾定义数组索引属性 - Error with splice method: can't define array index property past the end of an array with non-writable length 为什么函数声明会覆盖全局对象的不可写属性? - Why does a function declaration override non-writable properties of the global object?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM