Cloning any javascript object by copying all own properties - javascript

If I wanted to clone any javascript object (that's not null), I would think I could just copy all of its own properties (enumerable and non-enumerable) -- using Object.getOwnPropertyNames -- onto a new empty object.
But I've noticed that an example of a deep cloning function provided by Dojo toolkit (https://davidwalsh.name/javascript-clone) treats RegExp, Date, and Node objects as special cases, and lodash.cloneDeep also has a lot of logic that is a lot more complicated than simply copying properties, including having some special cases of its own and apparently not supporting all types of objects: (https://github.com/lodash/lodash/blob/master/.internal/baseClone.js).
Why is simply copying the object properties not sufficient? What else is there to a javascript object besides its properties that I don't know about?
EDIT: to be clear, I'm talking about deep cloning an object. Sorry for the confusion.

If the top level properties are all value objects like strings and numbers then just copying the top level properties is fine for a clone of an object. If there are any reference objects such as dates, arrays or other objects then all your are doing is copying a reference from one object to another. If you change the reference object on the clone you will mutate the original object.
Take a look at my clone function at https://stackblitz.com/edit/typescript-qmzgf7
If it is an array it clones every item in the array, if it is a date it creates a new date with the same time, if it is an object it clones every property else if just copies the property.
The cloned object can now be mutated without worrying about effects it might have on the original object.
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: (typeof obj === 'object') && obj
? Object.getOwnPropertyNames(obj).reduce((o, prop) => ({ ...o, [prop]: clone(obj[prop]) }), {})
: obj;
let original = { prop1: "Original", objProp: { prop1: "Original" } };
let swallowCopy = { ...original };
let clonedObj = clone(original);
clonedObj.prop1 = "Changed";
clonedObj.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
swallowCopy.prop1 = "Changed";
swallowCopy.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
Notice how modifying the property on the object property shallow copy causes the original to change as well.

The easiest way to clone an object in JS is by using the ... spread operator.
Let's say you have this object:
const object = { foo: 1, bar: 2 }
To clone it, you can simply declare:
const objectClone = {...object}.
This will create all the properties present in the original object onto the clone, as well as their values.
Now the problem is, if you have any object nested in there, the copies will be made by reference. Suppose the original object is this instead:
const student = { studentID: 1, tests: { test1: 90, test2: 95}}
If you create a copy of that object by using the spread operator(or Object.assign, spread is just syntactic sugar), the nested object will actually point to the object inside the original object! So repeating this:
const studentClone = {...student}
And now you edit a property of the nested object inside the clone:
studentClone.tests.test1 = 80
This will change the value in both clone, and original object, as the nested object is really just pointing to 1 object in memory.
Now what those utilities, like _.cloneDeep will do, is iterate through all inner objects in the object you're cloning, and repeat the process. You could technically do it yourself, but you wouldn't be able to do it on objects with many nested objects easily. Something like this:
const studentClone = {...studentClone, tests: {...studentClone.tests}}
This would create new objects, with no reference problems.
Hope this helped!
EDIT: Just adding, object spreading would only work properly for prototype objects, of course. Each instantiated objects,such as arrays, Date objects etc, would have their own way of cloning.
Arrays can be copied similarly, through [...array]. It does follow the same rules regarding to references. For dates, you can simply pass the original date object into the Date constructor again:
const clonedDate = new Date(date)
This is where the third-party utilities will come in handy, as they'll usually handle most use cases.

This answer does a good job of explaining two of the problems with cloning a normal JavaScript object: prototype properties and circular references. But to answer your question regarding certain built-in types, the TL;DR answer is that there are 'under the hood' properties that you have no programmatic access to.
Consider:
let foo = [1, 2];
let bar = {};
Object.assign(bar, foo);
Object.setPrototypeOf(bar, foo.constructor.prototype); // aka Array.prototype
bar[0]; // 1
bar instanceof Array; // true
bar.map(x => x + 1); // [] ????
Empty array? Why? Just to make sure we're not crazy
foo.map(x => x + 1); // [2, 3]
The reason why map (and the other array methods) fail to work is that an Array isn't simply an object: it has internal slot properties for the stuff you put in it that you don't get to see as the JavaScript programmer. As another example, every JavaScript object has an internal [[Class]] property that says what kind of object it is. Fortunately for us, there's a loophole in the spec that allows us indirect access to it: the good ol Object.prototype.toString.call hack. So let's see what that has to say about various stuff:
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(3); // [object Number]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(/\w/); // [object RegExp]
Object.prototype.toString.call(JSON); // [object JSON]
Object.prototype.toString.call(Math); // [object Math]
Let's see what it says about our foo and bar:
Object.prototype.toString.call(foo); // [object Array]
Object.prototype.toString.call(bar); // [object Object] Doh!
There's no way to 'convert' a random object to an Array... or a Date... or an HTMLElement... or a regex. Now, there are in fact ways to clone all of those things, but they require special logic: you can't just copy properties, or even set the prototype, because they have internal logic you can't access or directly replicate.
In normal everyday JavaScript programming we don't worry too much about this stuff, it's the kind of thing that's generally of interest to library authors (or language implementers). We everyday working stiffs just use a library to cover the edge cases and call it a day. But every once in a while the abstractions we use leak and the ugly bubbles through. This is however a great illustration of why you should probably use battle-tested libraries rather than trying to roll your own.

An object in javascript includes fields and functions together, and every field could be another object (Like Date type). If you copy a date field, it will be a reference type assignment.
Example:
var obj1 = { myField : new Date('2018/9/17') };
var obj2 = {};
obj2.myField = obj1.myField;
Now, if we change "obj2.myField" like this:
obj2.myField.setDate(obj2.myField.getDate() + 2);
console.log(obj1.myField); // Result =====> Wed Sep 19 2018 00:00:00 GMT+0430
As you see, obj1 and obj2 still are linked.
Correct way to copy a date field:
obj2.myField = new Date(obj1.myField.getTime());

Most native objects(like you have mentioned - I don't know for is the correct naming for them; maybe built-in?) are treated as "simple": it does not make sense to copy Date object property-by-property. In the same time they all are mutable in some way.
let a = {test: new Date(1)}; // test === Thu Jan 01 1970 00:00:00GMT
let copy_a = {test: a.test}; // looks like cloned
a.test.setDate(12); // let's mutate original date
console.log(copy_a.test); // Thu Jan 12 1970 00:00:00GMT ooops modified as well
So you either should handle that exceptions(special cases) explicitly or take a risk of side effects for some cases.

Related

Cloning arrays of objects with Object.assign

I discovered a bug on a project I'm working on that can be replicated by this snippet:
const original = [ { value: 1 } ];
function test() {
const copy = Object.assign([], original);
copy.forEach(obj => obj.value = obj.value + 1);
}
console.log(original[0].value); // -> 1, expected 1
test();
console.log(original[0].value); // -> 2, expected 1
test();
console.log(original[0].value); // -> 3, expected 1
I do not understand why this is the case. In the MDN web docs, the following statements can be found in the deep copy warning section:
For deep cloning, we need to use alternatives, because Object.assign() copies property values.
If the source value is a reference to an object, it only copies the reference value.
How do these notes apply to arrays / in this case? Are array values somehow considered as properties?
Looking back now, the method was probably not intended to work with arrays, so I guess I reap what I sow... but I'd still like to understand what's going on here. The intent was to deep copy the array in order to mutate the objects inside while keeping the original intact.
Are array values somehow considered as properties?
Yes. In JavaScript, arrays are objects (which is why Object.assign works with them), and properties with a special class of names called array indexes (strings defining decimal numbers in standard form with numeric values < 232 - 1) represent the elements of the array. (Naturally, JavaScript engines optimize them into true arrays when they can, but they're defined as objects and performing object operations on them is fully supported.) I found this sufficiently surprising when getting deep into JavaScript that I wrote it up on my anemic old blog.
Given:
const obj = {a: 1};
const arr = [1];
these two operations are the same from a specification viewpoint:
console.log(obj["a"]);
console.log(arr["0"]); // Yes, in quotes
Of course, we don't normally write the quotes when accessing array elements by index, normally we'll just do arr[0], but in theory, the number is converted to a string and then the property is looked up by name — although, again, modern JavaScript engines optimize.
const obj = {a: 1};
const arr = [1];
console.log(obj["a"]);
console.log(arr["0"]); // Yes, in quotes
console.log(arr[0]);
If you need to clone an array and the objects in it, map + property spread is a useful way to do that, but note the objects are only cloned shallowly (which is often sufficient, but not always):
const result = original.map((value) => ({...value}));
For a full deep copy, see this question's answers.
Here we can use structuredClone for deep copy.

Object.assign does not copy correctly

I'm working with VueJS.
I have a Method that receives a Object as argument.
Then I clone this Object with Object.assign().
Component.vue
export default {
// ...
methods: {
// ...
activateEditMode (item) {
this.editItemIndex = this.travelItinerary.indexOf(item)
this.editItem = Object.assign({}, item)
// ...
}
}
}
The original Object at this.roteiroCompleto[0]:
But when I edit the clone Object this.itemEditado:
the original Object this.roteiroCompleto[0] changes too.
I tried to copy each key and value, copy only the Array with .slice(), .map(a=>a), and nothing works. The two objects keep binding.
When I console.log(this.itemEditado), I get this:
The strange thing is, in another Vue Component, I use the same strategy, and it works.
Object.assign only does a shallow copy of the keys and values, meaning if one of the values in the object is another object or an array, then it is the same reference as was on the original object.
var x = { a: 10, b: { c: 100 } };
var y = Object.assign({}, x);
y.a = 20;
console.log( x.a, y.a ); // prints 10 20
y.b.c = 200;
console.log( x.b.c, y.b.c ) // prints 200 200
To deep copy an object, you can using something like the cloneDeep function in lodash or take an uglier approach using built in functions with JSON.parse( JSON.stringify( obj ) ).
Note that the second option will only work with primitive types that are supported by JSON.
If the methods you used isn't working well with objects involving data types, try this
import * as _ from 'lodash';
Deep clone object
myObjCopy = _.cloneDeep(myObj);
Solution from MDN
Object.assign(this.editItem, JSON.parse(JSON.stringify(item)))
In 2022, to deep clone objects natively on JavaScript you can use structuredClone
The global structuredClone() method creates a deep clone of a given
value using the structured clone algorithm.
MDN structuredClone()
You don't have to use a library, unless you really need a deep copy (I did not need one). Just do this:
this.editItem = {...item};
The ... operator will decompose item into its keys and values, and since you're doing that in an object literal (the { }), it uses those as the keys and values of the new object.
Might be helpful to other people who, like me, don't need a deep copy. Object.assign just straight-up doesn't work, and this does.

How to create a JS object with the default prototype from an object without a prototype?

Background: The module query-string is for example able to parse key=value&hello=universe to an object {key: 'value', hello: 'universe'}. However, the module author has decided that the returned object does not have a prototype. In other words, this "bastard" object is created by Object.create(null).
Problem: It would be convenient to use parsed.hasOwnProperty('hello') but that is not possible without the default object prototype. Of course, one could Object.prototype.hasOwnProperty.call(parsed, 'hello') but I think we can all agree that such an expression is kill-immediately-after-birth ugly.
Question: How to nicely convert the prototypeless object to have a default object prototype and methods such as hasOwnProperty? Additionally, can this be done without using the feared __proto__ or setPrototypeOf?
It would be convenient to use parsed.hasOwnProperty('hello') but that is not possible without the default object prototype
The whole point of creating such a "bastard object" is that you cannot do that - what if someone sent a query string ?hasOwnProperty=oops to your server?
How to nicely convert the prototypeless object to have a default object prototype and methods such as hasOwnProperty?
Don't. You should either use the long form with call, or just go for the in operator which does exactly what you need:
'hello' in parsed
In an ES6 environment, you might also want to convert the object to a proper Map and use it has method.
I wouldn't recommend changing the prototype of a third party package, it may be frozen and prone to runtime errors. I'd either use the built-in in operator as #Bergi suggested or the ES6 Reflect API.
const _ = console.info
const parsed = (
{ __proto__: null
, foo: 'fu'
, bar: 'bra'
}
)
_('foo' in parsed) // true
_('fu' in parsed) // false
/** ES6 (fully supported in current browsers) */
_(Reflect.has(parsed, 'foo')) // true
_(Reflect.has(parsed, 'fu')) // false
I can't say I've ever done this before, but here's one way to do it
let bastard = Object.create(null);
bastard.father = 'vader';
let adopted = Object.assign({}, bastard);
console.log(adopted.hasOwnProperty('father')); // => true
setPrototypeOf() is nothing to be feared of, yet without it you might do as follows;
var o1 = Object.create(null),
o2;
o1.test = "42";
o2 = Object.assign({},o1);
console.log(o2.test);
console.log(o2.constructor.prototype);

JavaScript objects used as hybrid data structures

I have a fairly decent knowledge of JavaScript and the prototypical inheritance that is used when initializing data structures, but I am still not completely sure how one of JS's unique functionalities works.
Lets say I create an array:
var myArr = [];
I can now push items to the array:
myArr.push('foo');
myArr.push('bar');
At this time myArr.length == 2
Now from there I can do something like
myArr['myProp'] = 5; // OR myArr.myProp = 5;
But my myArr.length still equals 2 and I can use some type of iteration method to iterate over the 2 values pushed initially.
So basically this object is a "hybrid" data structure that can be treated like an Array or an Object.
So my question is does the native Object syntax (myObj.someProperty = 'blah' OR myObj['someProperty'] = 'blah'), apply specifically to the Object.prototype and therefore ANY object inherited from that prototype? This would make sense because an Object's prototype chain looks like:
var myObj = {} -> Object.prototype -> null
And an Array's prototype chain looks like:
var myArr = [] -> Array.prototype -> Object.prototype -> null
Which would make me assume that anything you can do with an object (myObj.someProperty //as getter or setter) can be done with an Array which would then explain the phenomena I stated above.
To formalise my answer. This is exactly correct. Barring literals and temporary values pretty much everything in JavaScript is an object. Including functions, arrays, and variables.
It is for this exact reason that it is considered dangerous to iterate through an array with the syntax:
for (x in myArray){}
The above line of code could lead to unpredictable results!
This approach of creating all data types as objects allows JavaScript to be as dynamic and flexible as it is.
You can see the full description of Object here.

In JavaScript, how do I access an object inherited from Array with the [] operator?

I have a situation, where I need to create a new JavaScript object that is inherited from Array. I am using the following code:
// Create constructor function.
var SpecialArray = function () {};
// Create intermediate function to create closure upon Array's prototype.
// This prevents littering of native Array's prototype.
var ISpecialArray = function () {};
ISpecialArray.prototype = Array.prototype;
SpecialArray.prototype = new ISpecialArray();
SpecialArray.prototype.constructor = SpecialArray;
// Use Array's push() method to add two elements to the prototype itself.
SpecialArray.prototype.push('pushed proto 0', 'pushed proto 1');
// Use [] operator to add item to 4th position
SpecialArray.prototype[4] = 'direct [] proto to 4';
// Create new instance of Special Array
var x = new SpecialArray();
// Directly add items to this new instance.
x.push('pushed directly on X');
x[9] = 'direct [] to 9'
console.log(x, 'length: ' + x.length);
Quite interestingly, the [] operation seem to be useless and the console output is:
["pushed proto 0", "pushed proto 1", "pushed directly on X"] length: 3
What am I missing here?
It is not possible to subclass the Array class and use t this way.
The best solution for you is to extend just the array class and use it as it is.
There are two other options that I do not like but they exist
http://ajaxian.com/archives/another-trick-to-allow-array-subclasses
http://dean.edwards.name/weblog/2006/11/hooray/
This is one of those that always trips people up. The length property only applies to the ordered elements. You can't extend an array then insert an arbitrary non-sequitous key and expect it to work. This is because the relationship between the length property and the array contents is broken once you extend the array. Pointy's link above does a very good job of explaining this in more detail.
To prove this add this to the end of your example:
console.log(x[4]);
As you can see your entry is present and correct, it's just not part of the ordered array.
Like everything else in javascript the Array object is just a Associative Array with string keys. Non numerical, non sequitous keys are hidden to fool you into thinking it's a 'proper' numerically indexed array.
This strange mixed design of the Array object does mean you can do some strange and wonderful things like storing ordered and unordered information in the same object. I'm not saying this is a good idea, I'm just saying it's possible.
As you will have noticed by now when iterating structures like this the non sequitous keys don't appear which makes sense for the general use case of arrays for ordered information. It's less useful, or in fact useless when you want to get keyed info. I would venture that if ordering is unimportant you should use an object not an array. If you need both ordered and unordered store an array as a property in an object.
The best way I have found to create a child prototype of an "Array" is to not make a child prototype of "Array" but rather create a child of an "Array-Like" prototype. There are many prototypes floating around that attempt to mimic the properties of an "Array" while still being able to "inherit" from it, the best one I've found is Collection because it preserves the ability to use brackets []. The major downfall is that it doesn't work well with non-numeric keys (i.e. myArray["foo"] = "bar") but if you're only using numeric keys it works great.
You can extend this prototype like this:
http://codepen.io/dustinpoissant/pen/AXbjxm?editors=0011
var MySubArray = function(){
Collection.apply(this, arguments);
this.myCustomMethod = function(){
console.log("The second item is "+this[1]);
};
};
MySubArray.prototype = Object.create(Collection.prototype);
var msa = new MySubArray("Hello", "World");
msa[2] = "Third Item";
console.log(msa);
msa.myCustomMethod();

Categories