Why is my array being altered after using slice()? - javascript

I'm loading in a variable from my app-config.js file and then copying it with .slice() in an attempt to prevent its state from being mutated. Much to my chagrin, the function I'm using to alter data seems to be failing to respect this attempt at avoiding mutation. mySensitivityVarskeeps changing, although I'm not quite sure how since I'm only directly acting on mySeries. Any ideas as to why this is happening? Here's the code:
var mySeries = mySensitivityVars.slice();
//Dummy Processing Algorithm
function myDummyAlgo(sliderIndex, newValue, range) {
console.log(mySeries[sliderIndex].data)
var modifier = newValue/(range/2)
var newSeries = mySensitivityVars[sliderIndex].data.map(function(num){
return num * modifier
})
console.log(newSeries)
// mySeries[sliderIndex].data = newSeries
// console.log(sensitivityChart.series[0].data)
sensitivityChart.series[sliderIndex].setData(newSeries);
};

Slice can copy the array but any objects that are referenced inside the array are not getting copied (only a reference is copied)

Without seeing the contents of mySensitivityVars it's hard to tell, but my guess is you're mutating a reference to the original object rather than a duplicate object.
Do you have any objects in mySensitivityVars? If so, the corresponding objects in mySeries will be pointing to the original objects in mySensitivityVars rather than standalone duplicates, which is why you're probably seeing the mutation issues.

You should clone the array instead of copying it if you'd like it to be mutated. you can use JSON.parse(JSON.stringify(mySensitivityVars)) which is pretty fast deep cloning technic.
that would ensure new objects are assigned and not copy of the references.

When you do:
mySeries[sliderIndex].data newSeries;
The array mySeries is a copy of mySensitivityVars, but the objects in the array are not copies. Both arrays contain references to the same objects, and modifying the data property affects them both. You need to copy the objects as well:
mySeries = mySentitivyVars.map(o => Object.assign({}, o));
And if the objects contain references to other objects, you may need to do a deep copy. See What is the most efficient way to deep clone an object in JavaScript?

Related

Is cloning an object, map or array and then using delete for example, considered immutable?

If I have an existing object, array or map and I want to delete or add and item, is copying (shallow copy) a map first for example and then using the delete method on the new map, considered the correct way to maintain immutability?
EDIT
In functional languages I've learned like Elixir, data structures such as Lists return a new List. JS doesn't work this way. Even array methods like reduce under the hood are still taking an empty array as an initial parameter, and pushing items
(mutating the initial array) into it.
const map = new Map([
['dog', 'Dog'],
['cat', 'Cat'],
['chicken', 'Chicken'],
])
const map2 = new Map([
...map,
])
map2.delete('dog')
You are mutating map2. However, on reasonable encapsulation logic (such as putting the clone+delete in a function), it's still considered a pure operation, and the original that you pass as an argument would stay unmutated.
The functions
function withoutDog(map) {
const map2 = new Map(map);
map2.delete('dog');
return map2;
}
function withoutDog(map) {
return new Map(Array.from(map).filter(([key, _]) => key !== 'dog'));
}
are indistinguishable from the outside.
When something is immutable it simply means it does not change state.
Since you are not changing the object's state (i.e. object.foo = 'bar'), rather you are cloning it and mutating the clone, it is immutable.
No, this is not an example of immutability.
An object is immutable if it's not possible to modify its contents. Making a copy of an object allows you to modify the copy without modifying the original, but you can still modify the original if you want.
const map = new Map([
['dog', 'Dog'],
['cat', 'Cat'],
['chicken', 'Chicken'],
])
const map2 = new Map(map) // clone the Map
map2.delete('dog') // modify the clone -- doesn't affect the original
map.delete('cat') // modify the original -- it's not immutable
Is the OP asking if the data structure is immutable, or whether the patterns being used here are immutable? The question is inherently confusing because we typically use immutable as an adjective for data structures, not algorithm implementations; we typically describe algorithm implementations designed for use with immutable data structures as "pure functions" or "pure operations".
According to the usual definition of "immutable", #Barmar is correct that the answer is that the data structures in the question are not immutable, since objects in JS are mutable. Even when we use const declarations, the const keyword just makes the reference to the object immutable, but the values within the object can still be mutated; we're now holding an immutable, atomic reference to a mutable, compound value.
But the OP's wording ("is cloning ... considered immutable") suggests that really the question is asking whether the process in question is immutable. Therefore, #Bergi's answer is a good attempt to answer the question as it seems intended, by parsing "immutable" as "pure". If this logic were encapsulated in an API, that API would provide a pure operation / function to callers, even though the implementation would not be internally pure, since it modifies a local value before returning it.

Prevent object created by reference to change when the original one is changed

Probably this thing was talked before but I haven't find a solution to solve my problem yet.
So, my issue is this:
I'm saving into a variable the value of an object.
const aux = this.myObj;
the Object (this.myObj) is changed by some operations after that but I want to save its initial value and be able to re-assing it to it.
Something like:
this.myObj = aux
but as expected, the aux is also modified when the original one is.
Is there a solution to this?
You need to clone the object and save it's data to the other variable.
var clone = Object.assign({}, obj);
In case of nested object, Deep cloning can be achieved using various methods. One of that for a simple structured object with nested key-value pairs.
var clone = JSON.parse(JSON.stringify(obj));
or use library like underscore and lodash.
Object.assign({}, obj) simply copies property values of the given object so it wouldn't a good fix, I would recommend lodash's _.cloneDeep as it does deep cloning for you.
You would just have just to do the following.
const aux = _.cloneDeep(this.myObj)

Is there a "native" Javascript function that modifies an object property

Is there a native JavaScript function (or library/module for JavaScript) that modifies a key's value and then returns the full object?
Here's what I've done:
function modifyJSON(jsob, key, val) {
var newjsob = JSON.parse(JSON.stringify(jsob));
newjsob[key] = val;
return newjsob;
}
(Of course, if I wanted to modify the original, I could delete var newjsob... and work with jsob directly.)
This works, but if this type of functionality has already been implemented somewhere, I'd prefer to use that than roll my own.
Your title is a little misleading. What you're doing is deep cloning an existing object and then changing a property on the new object.
If shallow cloning is enough, you could use Object.assign:
var update = {};
update[key] = val;
return Object.assign(jsob, update);
If you need deep cloning, I would check out ImmutableJS. When updating any field on an immutable data structure, a clone of the original structure with your updates applied to it is returned. It's pretty much the exact behavior you're written.
Basically, the difficult part is the deep cloning. Lodash can deep clone the object for you.
https://lodash.com/docs/4.16.2#cloneDeep
function modifyJSON(jsob, key, val) {
var newjsob = _.cloneDeep(jsob);
newjsob[key] = val;
return newjsob
}
After you get the new object, you can just modify the object's property as you desire.
However, be aware that deep copying is slow. Only use it if you must.
https://jsfiddle.net/38kscyxa/

Javascript is changing variable that needs only to be referenced

I'm pulling some data from a reference object changing it around for what I need, but for some reason my code is also changing the object i'm referencing..
var obj = {name:"list of things", list:[{name:"thing", 1},{name:"other thing", 2}]};
function doStuff () {
var ref = obj;
for(var p=0;p<ref.list.length;p++){
ref.list.splice(1,1);
}
return ref;
}
For some reason where I'm using this structure in my code, its changing 'obj' as well as 'ref'. can't seem to figure it out
The obj in your example is not an object, it is a reference to an object that lives somewhere in memory. This is why when you do ref=obj, you get another reference to the same object, so changing the object ref references is the same as changing the object obj references.
What you want to do is clone your object, so you end up with two different objects. There are some good answers regarding cloning on StackOverflow and the whole web for that matter. Feel free to use any of those.
JavaScript assignes by reference. You would need to do a deep copy in order to clone an object.
See: http://webdevwonders.com/deep-copy-javascript-objects/

MongooseDocumentArray assignment

I have a MongooseDocumentArray found at this.prices. I can clear it with this.prices = [] and it retains all of its methods etc. and remains a MongooseDocumentArray. If I assign it to another variable first, e.g. array = this.prices, then array is also a MongooseDocuementArray, and changing one changes the other (i.e. they appear to be the same object). However, if I then attempt to clear that one with array = [], I find that array is now a plain, empty JS array. Doing array.length = 0 works fine, however. Can someone explain to me why this is and also how doing it with the original object works? I'm guessing this is more of a JS thing than a specifically Mongoose thing, but either way I'm perplexed.
When you first say:
this.prices = [];
... then mongoose is using what's known as a "setter" to intercept the assignment and cast it into a MongooseDocumentArray. Behind the scenes mongoose does this for all setting of paths on documents, not just document arrays. It uses Object.defineProperty to achieve this. Read more on that and its capabilities here.
What happens after that is more straightforward. When you then assign that to another variable:
array = this.prices;
... then you are assigning a reference to the cast this.prices object to array.
But when you say:
array = [];
... then you are changing that reference, causing array to point to a new Array object.
array.length = 0 on the other hand modifies the DocumentArray, but leaves the reference intact.
If you dig around in the source, particularly document.js and the various types, you can begin to figure out how mongoose handles this.

Categories

Resources