I'm setting an array to a property like this:
originalFacilityList: [...maintenanceInfo.Facilities]
However, when I check originalFacilityList downstream, it reflects the updates that were made to the maintenanceInfo.Facilities array. I was thinking that the spread operator was used to break that reference. Am I doing something incorrectly in this example?
When you do this originalFacilityList: [...maintenanceInfo.Facilities] you are effectively cloning the array. More precisely, you are doing a shallow clone, as opposed to deep clone.
As a result, when you add or remove items from the new array, the changes do not reflect on the original array:
const arr = [1, 2, 3];
const shallowClone = [...arr];
shallowClone.push(4);
console.log(shallowClone, arr);
What you have probably noticed is that the objects contained in the array are actually referenced by the old and the new array:
const arr = [{ property: 'value' }];
const shallowClone = [...arr];
arr[0].newProperty = 'newValue';
console.log(shallowClone);
If you want to avoid that, then you need to deep clone the array. There is no native solution for this, except some solutions like JSON.parse(JSON.stringify( that are very hacky and only work with serializable objects (does not preserve functions, prototypal inheritance, etc...), so either implement it yourself, or use utility libraries like Lodash's cloneDeep.
Related
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.
I am trying to check if an array of objects includes a object. I want it to return true when there is a object in the array that has the same values and the object id should not matter. This is how i thought it would work:
let arr = [{r:0, g:1}];
let obj = {r:0, g:1}
console.log(arr.includes(obj));
But it returns false and I need it to return true. Do I have to convert every object in the array to a string with JSON.stringify() and the object I am searching for like this:
let arr = [JSON.stringify({r: 0, g: 1})]
let obj = {r: 0, g: 1}
console.log(arr.includes(JSON.stringify(obj)));
Is there another easier and more efficient way to do it with more objects?
You get false because objects are compared by a reference to the object, while you got there 2 separate object instances.
Wile JSON.stringify might work, keep in mind that the order of properties is not guaranteed and it may fail if the order is not the same, because you get a different string.
you can check for an id property or compare several properties to match against, if you must you can compare all properties with a loop.
If you have an access to the object's reference, you can use a Map or a Set which allows you to store and check references
const obj = {r:0, g:1};
const obj2 = {r:0, g:1};
const mySet = new Set();
// given the fact that you do have access to the object ref
mySet.add(obj);
const isObjInList = mySet.has(obj);
const isObj2InList = mySet.has(obj2);
console.log('is obj in list - ', isObjInList);
console.log('is obj2 in list - ', isObj2InList);
JSON.stringify doesn't work as expected if you change the order of properties in one of the objects.
You can use .some in combination with isEqual from lodash (or other alternatives). Or you can write it by yourself, but be careful, there are too many edge cases, that's why I recommend using an existing approach. There is no need to reinvent the wheel.
let arr = [JSON.stringify({r: 0, g: 1})]
let obj = {g: 1, r: 0}
console.log(arr.includes(JSON.stringify(obj)));
let arr2 = [{r:0, g:1}];
let obj2 = {g:1, r:0};
console.log(arr2.some(item => _.isEqual(item, obj2)));
console.log(_.some(arr2, item => _.isEqual(item, obj2))); // more canonical way
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script>
I like to use Set() for this purposes, read from the documentation:
The Set object lets you store unique values of any type, whether primitive values or object references.
See the below example:
let obj = {r:0, g:1};
const set = new Set();
set.add(obj);
console.log(set.has(obj));
I hope that helps!
You can use the JavaScript some() method to find out if a JavaScript array contains an object.
This method tests whether at least one element in the array passes the test implemented by the provided function. Here's an example that demonstrates how it works:
// An array of objects
var persons = [{name: "Harry"}, {name: "Alice"}, {name: "Peter"}];
// Find if the array contains an object by comparing the property value
if(persons.some(person => person.name === "Peter")){
alert("Object found inside the array.");
} else{
alert("Object not found.");
}
Note that if try to find the object inside an array using the indexOf() method like persons.indexOf({name: "Harry"}) it will not work (always return -1). Because, two distinct objects are not equal even if they look the same (i.e. have the same properties and values). Likewise, two distinct arrays are not equal even if they have the same values in the same order.
The some() method is supported in all major browsers, such as Chrome, Firefox, IE (9 and above), etc. See the tutorial on JavaScript ES6 Features to learn more about arrow function notation.
I'm trying to get my head around how to use Immutables in JavaScript/TypeScript without taking all day about it. I'm not quite ready to take the dive into Immutable.js, because it seems to leave you high and dry as far as type safety.
So let's take an example where I have an Array where the elements are all of Type MyType. In my Class, I have a method that searches the Array and returns a copy of a matching element so we don't edit the original. Say now that at a later time, I need to look and see if the object is in the Array, but what I have is the copy, not the original.
What is the standard method of handling this? Any method I can think of to determine whether I already have this item is going to take some form of looping through the collection and visiting each element and then doing a clunky equality match, whether that's turning both of them to strings or using a third-party library.
I'd like to use Immutables, but I keep running into situations like this that make them look pretty unattractive. What am I missing?
I suspect that my solution is not "...the standard method of handling this." However, I think it at least is a way of doing what I think you're asking.
You write that you have a method that "...returns a copy of a matching element so we don't edit the original". Could you change that method so that it instead returns both the original and a copy?
As an example, the strategy below involves retrieving both an original element from the array (which can later be used to search by reference) as well as a clone (which can be manipulated as needed without affecting the original). There is still the cost of cloning the original during retrieval, but at least you don't have to do such conversions for every element in the array when you later search the array. Moreover, it even allows you to differentiate between array elements that are identical-by-value, something that would be impossible if you only originally retrieved a copy of an element. The code below demonstrates this by making every array element identical-by-value (but, by definition of what objects are, different-by-reference).
I don't know if this violates other immutability best practices by, e.g., keeping copies of references to elements (which, I suppose, leaves the code open to future violations of immutability even if they are not currently being violated...though you could deep-freeze the original to prevent future mutations). However it at least allows you to keep everything technically immutable while still being able to search by reference. Thus you can mutate your clone as much as you want but still always hold onto an associated copy-by-reference of the original.
const retrieveDerivative = (array, elmtNum) => {
const orig = array[elmtNum];
const clone = JSON.parse(JSON.stringify(orig));
return {orig, clone};
};
const getIndexOfElmt = (array, derivativeOfElement) => {
return array.indexOf(derivativeOfElement.orig);
};
const obj1 = {a: {b: 1}}; // Object #s are irrelevant.
const obj3 = {a: {b: 1}}; // Note that all objects are identical
const obj5 = {a: {b: 1}}; // by value and thus can only be
const obj8 = {a: {b: 1}}; // differentiated by reference.
const myArr = [obj3, obj5, obj1, obj8];
const derivedFromSomeElmt = retrieveDerivative(myArr, 2);
const indexOfSomeElmt = getIndexOfElmt(myArr, derivedFromSomeElmt);
console.log(indexOfSomeElmt);
The situation you've described is one where a mutable datastructure has obvious advantages, but if you otherwise benefit from using immutables there are better approaches.
While keeping it immutable means that your new updated object is completely new, that cuts both ways: you may have a new object, but you also still have access to the original object! You can do a lot of neat things with this, e.g. chain your objects so you have an undo-history, and can go back in time to roll back changes.
So don't use some hacky looking-up-the-properties in the array. The problem with your example is because you're building a new object at the wrong time: don't have a function return a copy of the object. Have the function return the original object, and call your update using the original object as an index.
let myThings = [new MyType(), new MyType(), new MyType()];
// We update by taking the thing, and replacing with a new one.
// I'll keep the array immutable too
function replaceThing(oldThing, newThing) {
const oldIndex = myThings.indexOf(oldThing);
myThings = myThings.slice();
myThings[oldIndex] = newThing;
return myThings;
}
// then when I want to update it
// Keep immutable by spreading
const redThing = myThings.find(({ red }) => red);
if (redThing) {
// In this example, there is a 'clone' method
replaceThing(redThing, Object.assign(redThing.clone(), {
newProperty: 'a new value in my immutable!',
});
}
All that said, classes make this a whole lot more complex too. It's much easier to keep simple objects immutable, since you could simple spread the old object into the new one, e.g. { ...redThing, newProperty: 'a new value' }. Once you get a higher than 1-height object, you may find immutable.js far more useful, since you can mergeDeep.
Is there some difference between using Array.from(document.querySelectorAll('div')) or [...document.querySelectorAll('div')]?
Here is a example:
let spreadDivArray = [...document.querySelectorAll('div')];
console.log(spreadDivArray);
let divArrayFrom = Array.from(document.querySelectorAll('div'));
console.log(divArrayFrom);
The console.log() will log the same result.
Is there any performance difference?
Spread element (it's not an operator) works only with objects that are iterable (i.e. implement the ##iterator method). Array.from() works also on array-like objects (i.e. objects that have the length property and indexed elements) which are not iterable. See this example:
const arrayLikeObject = { 0: 'a', 1: 'b', length: 2 };
// This logs ['a', 'b']
console.log(Array.from(arrayLikeObject));
// This throws TypeError: arrayLikeObject[Symbol.iterator] is not a function
console.log([...arrayLikeObject]);
Also, if you just want to convert something to array, I think it's better to use Array.from() because it's more readable. Spread elements are useful for example when you want to concatenate multiple arrays (['a', 'b', ...someArray, ...someOtherArray]).
Well, Array.from is a static method, i.e., a function whereas the spread syntax is part of the array literal syntax. You can pass functions around like data, you can invoke them once, several times or not at all. This isn't possible with the spread syntax, which is static in this regard.
Another difference, which #nils has already pointed out, is that Array.from also works with array-like objects, which don't implement the iterable protocol. spread on the other hand requires iterables.
The difference is that spread allows an array to be expanded. Whereas from() creates a new array. .from() doesn't expand upon anything, it creates a new array based on the data provided; the spread operator on the other hand can expand an array with new properties.
If the input is iterable they do the exact same thing.
However, based on the benchmarks, the spread operator seems to perform better for a Set.
https://jsben.ch/5lKjg
let set = new Set();
for (let i = 0; i < 10000; i++) {
set.add(Math.random());
}
let tArrayFrom = window.performance.now()
let arr = Array.from(set)
console.log("Array.from():", window.performance.now() - tArrayFrom + "ms")
// slightly faster in most of the runs:
let tSpread = window.performance.now()
let arr2 = [...set];
console.log("Spread syntax:", window.performance.now() - tSpread + "ms")
Using Babel is a good way to see what's happening internally.
Heads up, though. Make sure latest is selected in Babel, as the default is wrong.
Using your example above, this is the output.
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var spreadDivArray = [].concat(_toConsumableArray(document.querySelectorAll('div')));
console.log(spreadDivArray);
var divArrayFrom = Array.from(document.querySelectorAll('div'));
console.log(divArrayFrom);
I need to clarify everyone's answers:
The ...foo syntax just spreads (expands) all array values as if they were separate, comma-separated arguments. It does a shallow spread. Any primities (numbers, strings etc) are COPIED and any complex values (objects) are instead REFERENCED.
The [] around it is what CREATES a new array.
So [...foo] will create a new array and populate it by doing a SHALLOW COPY spreading of all array elements as if they were array constructor arguments which in turn takes all those copied elements and puts them in the new array.
Whereas Array.from(foo) will CREATE a new array using the input variable, but is A LOT FASTER because it ONLY creates a SHALLOW COPY (this is FASTER). So it takes the exact input and just puts every variable/reference into the new array.
Use Array.from().
I've been surfing around here a while and still haven't found an answer that worked for me.
Is there any way to deep copy a non-plain object in JS?
I've tried jQuery.extend(true, {}, this) but it only cloned some of it, the rest remained as a reference to another object.
Here are 3 different methods for copying objects. Each method has pros and cons, so read through and pick the best for your situation
Object.assign method
Use Object.assign, which "is used to copy the values of all enumerable own properties from one or more source objects to a target object". This copies both values and functions. At the time of writing this, browser support is good but not perfect, but this is the best method IMO of the three.
const obj1 = {a:1, b:2};
const obj1Copy = Object.assign(obj1)
Spread operator method
Alternatively, you can use the spread operator to spread from one object into another. Keep in mind that this will copy the values of keys, but if you the value of a key is a memory address (an other nested object or an array) then it will only be a shallow copy.
const obj1 = {a: () => {}, b:2}
const obj1Copy = { ...obj1 }
JSON stringify/parse trick
If the object doesn't have any circular references or functions as values, you can use the json stringify trick:
let myCopy = JSON.parse(JSON.stringify(myObject));
No libraries required, and works very well for most objects.
You can use lodash's cloneDeep function - https://lodash.com/docs/4.16.4#cloneDeep
Example (from docs)
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
A quick method to clone objects deep with performance into consideration.
JSON.parse(JSON.stringify({"foo":"bar"}))
How about performance ? >> [ May be this is the best way to deep copy objects ]. I strongly recommend you to checkout this video from Google Chrome Developers community on Youtube explaining how this method works and performance benchmarks.
Note: Use the JSON.parse method if your objects don't have Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types.
Source : Read this SO answer
Quick tip - React.JS initial state tree can be loaded from localStorage using this solution.
If you are dealing with a class instance you could use something like this.
You wouldn't need to copy the functions as they are delegated to on the prototype.
// myObject constructor
function myObject(foo, bar){
this.foo = foo
this.bar = bar
}
// delegate the functions to a prototype
myObject.prototype.something = function(){
console.log('something')
}
function instanceCopy(obj) {
// copy the object by the constructor
const copy = new obj.constructor()
const keys = Object.keys(obj)
keys.forEach(key => {
copy[key] = obj[key]
})
return copy
}
const myObj = new myObject('foo', 'bar')
const copyObj = instanceCopy(myObj)
console.log('myObj', myObj)
console.log('copyObj', copyObj)
console.log('same ?', copyObj === myObj)
// can we still call the functions
copyObj.something()
<script src="https://codepen.io/synthet1c/pen/WrQapG.js"></script>
You could use the structuredClone method:
const cloned = structuredClone(object)
Anyway, structuredClone allows you to do also other things that you might be interested in.
Check the documentation for further details:
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
Lodash _.cloneDeep() method kills the application performance. So I have come up with basic JavaScript solution. I have added it to my GIT repo. My application performance is back to normal after using my solution.
https://github.com/manideeppabba1991/javascript_util_functions/blob/master/clone_Array_or_Object.js