Skip to content Skip to sidebar Skip to footer

Can I Use Destructuring To Create A Deep Copy?

Basically I want to get a shallow deep copy that won't change my main object using destructuring. let a = { name: 'lala', testArray: [1,2,3], object: { name: 'object'

Solution 1:

You can't create new objects with destructuring, no. You can only pick out values that exist on the source, you can't perform transformations on them. (You can change the variable name you use, but you can't transform the value.) I've often wanted to, but you can't (at least, not at present).

There are various jump-through-the-hoops ways you could do it, but really the simplest is going to be to make a shallow copy of the array separately.

A simpler example:

const obj = {
  foo: "bar",
  array: [1, 2, 3]
};

const {foo} = obj;
const array = obj.array.slice(); // or: = [...obj.array];

obj.array[0] = "one";
console.log(obj.array[0]); // "one"
console.log(array[0]);     // 1

Solution 2:

It is not possible directly.

let { object } = a;
object = {...object}; // Object.assign({}, object); <<

object.array = [0, ...object.array, 0];

console.log(object.array); // [0, 4, 5, 6, 0]
console.log(a.object.array); // [4, 5, 6]

Solution 3:

Answer

You can achieve this by using a Proxy Object, either through a function, or directly.


Using a Function during Destruct:

The Proxy object takes the target object, and a handler.

A handler allows you to set certain conditions like get and set that can alter the way that data is returned to the user. We'll be using get for this.

In the below handler, we alter the get functionality. We check if the target[prop] returns an Array or Object. If it does, we create a copy in memory and return that instead of the reference. If it is not an Array or Object we simply return the primitive value (string, number, etc)

let copyHandler = {
  get: function( target, prop, receiver ) {
    let value = target[ prop ];
    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }
}, getCopy = obj => new Proxy(obj, copyHandler);

Utilizing the getCopy function as our destructuring middle-man, we can be sure that all our values return new references:

const {
  name,
  testArray,
  object
} = getCopy(a);


object.array = [...object.array, 0];
console.log(a.object.array); // [4,5,6]
console.log(object.array); // [4,5,6,0]

Example:

let copyHandler = {
  get: function( target, prop, receiver ) {
    let value = target[ prop ];
    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }
}, getCopy = obj => new Proxy(obj, copyHandler);



let a = {
  name: 'lala',
  testArray: [ 1, 2, 3 ],
  object: {
    name: 'object',
    array: [ 4, 5, 6 ]
  }
};

const {
  name,
  testArray,
  object
} = getCopy(a);



object.array = [...object.array, 0];
console.log(a.object.array); // [4,5,6]
console.log(object.array); // [4,5,6,0]

Alternatively, we can do it directly on Declaration/Initialization/Reception:

Directly in this sense means that we can setup the Object to return copies during destructured declaration only.

We do this similarly to above by utilizing a Proxy, and a middle-man function.

Note: The middle-man functions aren't necessary, but it helps keep things organized.

let destructHandler = {
  get: function( target, prop, receiver ) {
    if(!this.received) this.received = new Set();

    let value = target[ prop ];
    if(this.received.has(prop)) return value;

    this.received.add(prop);

    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }, destructable = obj => new Proxy(obj, destructHandler);

The difference here is that our get handler uses a Set to determine whether or not a property has already been grabbed once.

It will return copies upon the first request for a referential property (an Array or Object). It will still return any primitive value as normal.

This means that upon declaring/initialization/reception of the object, you can apply the destructable proxy and immediately afterwards pull out copies from that object using a destruct.

Initialization example code:

let a = destructable({
  name: 'lala',
  testArray: [ 1, 2, 3 ],
  object: {
    name: 'object',
    array: [ 4, 5, 6 ]
  }
});

Destructure example:

const {
  name,
  testArray,
  object
} = a;

object.array = [...object.array, 0];
console.log(a.object.array); // [4,5,6]
console.log(object.array); // [4,5,6,0]

Example:

let destructHandler = {
  get: function( target, prop, receiver ) {
    if(!this.received) this.received = new Set();
    
    let value = target[ prop ];
    if(this.received.has(prop)) return value;
    
    this.received.add(prop);
    
    if ( Array.isArray( value ) ) return value.slice( 0 );
    if ( typeof value === "object" && value.constructor.name === "Object" ) return Object.assign( {}, value );
    return value;
  }
}, destructable = obj => new Proxy(obj, destructHandler);


let a = destructable({
  name: 'lala',
  testArray: [ 1, 2, 3 ],
  object: {
    name: 'object',
    array: [ 4, 5, 6 ]
  }
});

const {
  name,
  testArray,
  object
} = a;
    object.array = [...object.array, 0];
    console.log(object.array); // [4,5,6,0]
    console.log(a.object.array); // [4,5,6]

Hope this helps! Happy Coding!


Post a Comment for "Can I Use Destructuring To Create A Deep Copy?"