64

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.

Ivar
  • 6,138
  • 12
  • 49
  • 61
ShivangiBilora
  • 2,912
  • 4
  • 20
  • 27
  • 1
    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" – Sebas Dec 29 '15 at 05:05
  • Lodash's _.clonedeep does a deep copy and works as expected https://lodash.com/docs/4.17.11#cloneDeep – Christopher Tokar Nov 07 '18 at 08:23
  • It's a shallow copy. I don't know why nobody wrote this earlier. – MrMesees May 15 '22 at 02:49

7 Answers7

38

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
Ramanlfc
  • 8,283
  • 1
  • 18
  • 24
36

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.

Rish
  • 1,303
  • 9
  • 22
  • 24
    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? – stackjlei Sep 15 '16 at 23:35
  • 3
    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. – Stu Feb 05 '18 at 19:17
15

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
Marian07
  • 2,303
  • 4
  • 27
  • 48
  • 1
    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. – Raphael Alvarenga Jan 19 '20 at 18:52
  • Great! I’m glad that it helped you – Marian07 Jan 20 '20 at 04:48
  • 7
    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. – terrymorse Feb 02 '20 at 23:18
  • 1
    It will not work if you're having object with method/function – Ram Ratan Bairar Jul 22 '22 at 12:20
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).

Bihn Kim
  • 314
  • 2
  • 5
  • 6
    Couldn't an app's redux state contain objects with references to other objects..? – Victor Zamanian Aug 15 '17 at 10:47
  • @VictorZamanian It definitely can, so Object.assign may not be the right tool depending on the data structure. I find [Immutability-helper](https://github.com/kolodny/immutability-helper) particularly useful for Redux states. It only performs a deep copy on what you change and uses shallow copies for everything else. – Burak May 03 '18 at 13:28
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);
Saqib Naseeb
  • 731
  • 1
  • 9
  • 21
2

Object.assign create a Shallow Copy only.

const originalObject = {
        api : 'POST',
        contentType : 'JSON',
        userData : {
            name : 'Triver',
            email : 'postaction@yahoo.com'
        },
    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": "postaction@yahoo.com"
    },
    "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.

Kms
  • 1,082
  • 2
  • 11
  • 27
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

terrymorse
  • 6,771
  • 1
  • 21
  • 27
  • try it with `const original = { name: {f:""} }; const copied = shallowClone(original); copied.name.f = "m";` original.name.f is now "m" – Purefan Dec 29 '21 at 10:44