15

In a project I'm working on, I need to clone an object to a variable.

I first tried - what seemed to be the most obvious solution - to do var obj2 = obj1, however I soon realized this makes obj2 refer to obj1, so whenever I set a property in obj2, the property is updated in obj1, too. Well, I can't have that.

So, I started searching around for ways to clone a object in JavaScript - I found multiple solutions for this, mainly var obj2 = JSON.parse(JSON.stringify(obj1)) - but that didn't keep all getters and setters I had defined for my object!

The now most obvious solution to me seems to firstly use the JSON trick above to make obj2 have all of obj1's properties, then loop through all of the objects getters and setters and add them back using Object.defineProperty(), but I have yet to find a way to get all getters/setters of an object.

Arthur F.
  • 180
  • 1
  • 9
  • 1
    Check out https://lodash.com/docs#clone – AlexD Dec 27 '15 at 13:51
  • You'll need to create your own `clone` method on the object you want which creates a new object with the same constructor and assigns the same values to it. – Louay Alakkad Dec 27 '15 at 13:59
  • @AlexD lodash's `clone` won't work - like any non-ES5-aware cloning function when it attempts to copy a property supplied by a "getter" it simply reads the property's current value and stores that in the clone instead of copying the getter function itself. – Alnitak Dec 27 '15 at 14:23
  • @ALexD - yep - Alnitak is 100% correct. Does not copy the getter only uses it to access the current value and clones the value directly... – Moonwalker Dec 31 '15 at 16:35

2 Answers2

38

At a practical level it's impossible to 100% accurately clone an object since the getters and setters (and indeed other functions) may be accessing lexically-scoped private variables via closures. Accessing such a method will reference the original object, not the clone.

If (and only if) that's not the case, you can just enumerate over all properties (even non-enumerable ones) found via Object.getOwnPropertyNames() and then for each name simply obtain the individual PropertyDescriptors with Object.getOwnPropertyDescriptor and then pass the resulting field to Object.defineProperty, e.g.:

function shallowClone(obj) {
    var clone = Object.create(Object.getPrototypeOf(obj));

    var props = Object.getOwnPropertyNames(obj);
    props.forEach(function(key) {
        var desc = Object.getOwnPropertyDescriptor(obj, key);
        Object.defineProperty(clone, key, desc);
    });

    return clone;
}

In ES2017 you can do this instead:

function shallowClone(obj) {
    return Object.create(
        Object.getPrototypeOf(obj), 
        Object.getOwnPropertyDescriptors(obj) 
    );
}
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Thank you very much! This is precisely what I was looking for - I'm working on the project with two others, and the leader has a strict "no library" rule. ;) – Arthur F. Dec 27 '15 at 14:04
  • @ArthurF. please note that this code is ES5 only - it won't work on anything prior to IE9 (from memory) – Alnitak Dec 27 '15 at 14:11
  • @ArthurF. I just corrected a minor error in the `Object.create` line – Alnitak Dec 27 '15 at 14:28
  • Hey - I'm sorry for being a bit late with this reply, but as a bit of a JS newbie, what are "lexically-scoped private variables"? Can you give an example of this? Thanks. :) – Arthur F. Dec 27 '15 at 14:36
  • @ArthurF. see "how do JavaScript closures work" - linked to on the right under "related". – Alnitak Dec 27 '15 at 14:39
  • @Alnitak The _related_ section changes over time. Add a direct link next time, as the link you refer to might not be there in the future. – dotnetCarpenter May 01 '17 at 13:55
  • @ArthurF. Here is the link to **How do JavaScript closures work**: http://stackoverflow.com/questions/111102/how-do-javascript-closures-work – dotnetCarpenter May 01 '17 at 13:56
  • This is a simple example of "lexically-scoped private variable" : `let a = {}; (function buildA() { let pvtAValue = 42; Object.defineProperty(a, "value", get: function() { return pvtAValue; }, set: function() { /* do nothing */ }, configurable: false }); /* other code here to manipulate pvtAValue internally */ })();` This will create a readonly property `a.value` which when cloned will just return 42 and you have no easy way to replicate the logic behind manipulating the scoped `pvtAValue` (if that matters, which it usually does when you don't want library dependency) – Karl Stephen Sep 25 '20 at 04:32
  • This code implements a shallow clone of an object, which means it creates a new object with the same properties as the original object but does not deeply copy any properties that are objects or arrays. This means that any changes made to these properties in the cloned object will also be reflected in the original object. To make a deep clone, you would need to modify this code to recursively call shallowClone on any properties that are objects or arrays. – Mark Anthony Libres Feb 05 '23 at 15:47
1

by @Alnitak, I thinks that is shortcut way

function shallowClone(obj){
 var clone = Object.create(Object.getPrototypeOf(obj));
 var descriptors = Object.getOwnPropertyDescriptors(obj);
 Object.defineProperties(clone, descriptors);
 return clone;
}
pery mimon
  • 7,713
  • 6
  • 52
  • 57
  • Yes, but this is ES2017 and later only, so only works if `Object.gteOwnPropertyDescriptors()` exists, which e.g it doesn't in Edge < 15. – Alnitak Aug 03 '18 at 12:17
  • sorry I can't track all Edge version ... the above is the right answer hop Edge one day catch up – pery mimon Aug 04 '18 at 21:38