Does Object.assign() create a deep copy or a shallow copy?
Asked Answered
B

7

71

I just came across this concept of

var copy = Object.assign({}, originalObject);

which creates a copy of original object into the "copy" object. However, my question is, does this way of cloning object create a deep copy or a shallow copy?

PS: The confusion is, if it creates a deep copy, then it would be the easiest way to clone an object.

Banquette answered 29/12, 2015 at 5:0 Comment(3)
doc is self-explanatory I guess: "The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object"Whichsoever
Lodash's _.clonedeep does a deep copy and works as expected lodash.com/docs/4.17.11#cloneDeepConstantino
It's a shallow copy. I don't know why nobody wrote this earlier.Argali
D
39

Forget about deep copy, even shallow copy isn't safe, if the object you're copying has a property with enumerable attribute set to false.

MDN :

The Object.assign() method only copies enumerable and own properties from a source object to a target object

take this example

var o = {};

Object.defineProperty(o,'x',{enumerable: false,value : 15});

var ob={}; 
Object.assign(ob,o);

console.log(o.x); // 15
console.log(ob.x); // undefined
Despairing answered 29/12, 2015 at 5:8 Comment(2)
This doesn't quite answer the question. Rather it says what will happen if it's non-enumerable.Skateboard
Doesn't address the actual question.Draff
F
40

By using Object.assign(), you are actually doing Shallow Copy of your object. Whenever we do an operation like assigning one object to other, we actually perform a shallow copy, i.e. if OBJ1 is an object, modifying it through another object which is OBJ2 will reflect changes in OBJ1 too.

Farrica answered 29/12, 2015 at 5:14 Comment(3)
if it only makes a shallow copy, how does Redux work? I thought the whole point of it was to make a deep copy of the data that is dispatched so that if the data is changed outside the store, it won't be change what's in the store also. if it were a shallow copy, then the data would be linked, which causes issues with the data changing what's in the store even without a dispatch, correct?Hamer
I ran into this same problem in Redux and i ended up going to JSON.parse(JSON.stringify()). This also has problems if the object is mutated by another package and creates recursive issues. I am looking for a better alternative. This method is still the one i am using.Trinette
function myReducer(state =value , action) { return new state); reducer function is a pure function and it always return a new state.that's why deep copy is not mandatory.you just need to make sure you are copying it perfectly by following deep copy or shallow copy approach.at the end ,you are getting new state from reducer.Damian
D
39

Forget about deep copy, even shallow copy isn't safe, if the object you're copying has a property with enumerable attribute set to false.

MDN :

The Object.assign() method only copies enumerable and own properties from a source object to a target object

take this example

var o = {};

Object.defineProperty(o,'x',{enumerable: false,value : 15});

var ob={}; 
Object.assign(ob,o);

console.log(o.x); // 15
console.log(ob.x); // undefined
Despairing answered 29/12, 2015 at 5:8 Comment(2)
This doesn't quite answer the question. Rather it says what will happen if it's non-enumerable.Skateboard
Doesn't address the actual question.Draff
O
21

For small Data structures I see that JSON.stringify() and JSON.parse() work nice.

// store as JSON
var copyOfWindowLocation = JSON.stringify(window.location)
console.log("JSON structure - copy:", copyOfWindowLocation)
// convert back to Javascript Object
copyOfWindowLocation = JSON.parse(copyOfWindowLocation)
console.log("Javascript structure - copy:", copyOfWindowLocation)

In 2023 there is now a official JavaScript way for deep clone of objects structuredClone()

But I recommend you to read the documentation and do a few tests because certain clones don't work. https://developer.mozilla.org/en-US/docs/Web/API/structuredClone

For example this throws a error:

var clone = structuredClone(window.location) 

The error

VM673:1 Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': Location object could not be cloned.
at <anonymous>:1:13
Ostrowski answered 10/1, 2018 at 20:26 Comment(4)
Your solution worked great for me. It was being frustrating for me until I read this simple stringify and parse back solution. Thanks a lot.Yonne
Great! I’m glad that it helped youOstrowski
Take caution. JSON.stringify() ... JSON.parse() strips out object properties that were set to undefined, converts Date objects to a date string, and coverts regular expressions to {}. And it will convert whatever "type" the original object was to a plain Object.Strouse
It will not work if you're having object with method/functionChaqueta
E
14

It creates a shallow copy, according to this paragraph from MDN:

For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.

For the purposes of redux, Object.assign() is sufficient because the state of a redux app only contains immutable values (JSON).

Extrauterine answered 23/1, 2017 at 9:56 Comment(2)
Couldn't an app's redux state contain objects with references to other objects..?Seamstress
@VictorZamanian It definitely can, so Object.assign may not be the right tool depending on the data structure. I find Immutability-helper particularly useful for Redux states. It only performs a deep copy on what you change and uses shallow copies for everything else.Sioux
T
2
var copy = Object.assign({}, originalObject);

does a shallow copy which is changing the copy reflect changes in your original object also. So to perform deep copy I would recommend the lodash cloneDeep

import cloneDeep from 'lodash/cloneDeep';
var copy = cloneDeep(originalObject);
Turkic answered 13/12, 2019 at 11:32 Comment(1)
Isn't loadash outdated? I read some blogs about it is obsolete after ES6Olmos
H
2

Object.assign create a Shallow Copy only.

const originalObject = {
        api : 'POST',
        contentType : 'JSON',
        userData : {
            name : 'Triver',
            email : '[email protected]'
        },
    responseTime: '10ms'
}

const originalObjectRef = Object.assign({}, originalObject);
originalObjectRef.contentType = 'XHTML';
originalObjectRef.userData.name = 'Red John';
console.log(originalObject);

Output:
{
    "api": "POST",
    "contentType": "JSON",
    "userData": {
        "name": "Red John",
        "email": "[email protected]"
    },
    "responseTime": "10ms"
}

In shallow copy, a reference variable mainly stores the address of the object it refers to. When a new reference variable is assigned the value of the old reference variable, the address stored in the old reference variable is copied into the new one. This means both the old and new reference variable point to the same object in memory. As a result if the state of the object changes through any of the reference variables it is reflected for both.

Note: Below is the ES6 way of shallow copy.

const originalObjectRef = {...originalObject};

Hope this might help someone, Thanks.

Horrible answered 21/4, 2022 at 17:19 Comment(0)
S
1

As mentioned above, Object.assign() will do a shallow clone, fail to copy the source object's custom methods, and fail to copy properties with enumerable: false.

Preserving methods and non-enumerable properties takes more code, but not much more.

This will do a shallow clone of an array or object, copying the source's methods and all properties:

function shallowClone(src) {
  let dest = (src instanceof Array) ? [] : {};

// duplicate prototypes of the source
  Object.setPrototypeOf(dest, Object.getPrototypeOf(src));

  Object.getOwnPropertyNames(src).forEach(name => {
    const descriptor = Object.getOwnPropertyDescriptor(src, name);
    Object.defineProperty(dest, name, descriptor);
  });
  return dest;
}

Example:

class Custom extends Object {
  myCustom() {}
}

const source = new Custom();
source.foo = "this is foo";
Object.defineProperty(source, "nonEnum", {
  value: "do not enumerate",
  enumerable: false
});
Object.defineProperty(source, "nonWrite", {
  value: "do not write",
  writable: false
});
Object.defineProperty(source, "nonConfig", {
  value: "do not config",
  configurable: false
});

let clone = shallowClone(source);

console.log("source.nonEnum:",source.nonEnum);
// source.nonEnum: "do not enumerate"
console.log("clone.nonEnum:", clone.nonEnum);
// clone.nonEnum: – "do not enumerate"

console.log("typeof source.myCustom:", typeof source.myCustom);
// typeof source.myCustom: – "function"
console.log("typeof clone.myCustom:", typeof clone.myCustom);
// typeof clone.myCustom: – "function"

jsfiddle

Strouse answered 3/2, 2020 at 17:39 Comment(1)
try it with const original = { name: {f:""} }; const copied = shallowClone(original); copied.name.f = "m"; original.name.f is now "m"Septenary

© 2022 - 2024 — McMap. All rights reserved.