Why the two scripts behave differently? I want the to use the first script, but in the second drawData() call it changes data; it's weird and not what I want to happen. The second script does not have this problem. Why is it like that, and how can I fix the first script?
First script does not change data:
var data = ["right"];
function drawData(arrs, type) {
if (type == "percentage") {
arrs[0] = "omg";
}
console.log(data[0]); // Changed!?
}
drawData(data);
drawData(data, "percentage");
Second script:
var data = "right";
function drawData(arrs, type) {
if (type == "percentage") {
arrs = "omg";
}
console.log(data); // OK, not changed.
}
drawData(data);
drawData(data, "percentage");
This is the difference between assignment to a local variable and mutation of the given object.
In both pieces of code arrs is a local variable, distinct from data. But the elements and other properties of arrs are exactly the same as those of data. Making changes to those property values (which is commonly called mutation of the object/array) will be visible whether you access them via arrs or via data. And this is exactly what the first script does.
The second script however, does not change a property value of arrs, but assigns an entirely new value to arrs, so that now it does not share any properties any more with data. This is even more apparent, because both data and arrs are primitive values which cannot mutate like explained in the previous paragraph. But even if they were objects or arrays, and you would do the following assignment:
arrs = [1234];
It would not affect data. data would only be affected if you would assign to a property/index of arrs without assigning to arrs directly.
First variant modifies object passed as parameter to function (which happens to be array) - so this change is seen outside function. Second variant assigns new value to function parameter (which happens to be reference to array) but does not change array itself.
Related
I have the following function
export const massageData = (data) => ({
title: data?.label,
sections: data?.sections?.map((item) => {
if (item.display === "HR") {
const tableBody = [{ id: "new" }];
item["tableData"] = tableBody;
}
return item;
}),
});
Here, what is happing I am updating the item , here data is getting updated and this if condition is getting called two times. also I am updating item with new value, but when returning it gives me undefined.
Any help will be helpful .
The behavior whereby the original data is being updated after you mutate where the data is copied lies in the distinction between Value Types and Reference Types, which are the two broad categories that all data types in JavaScript fall into. For value types, it means that a value is simply a value and not the value retrieved from pointing to a position in memory. However, reference types are those data types whose value is not directly stored, but rather what is stored is a reference to a point in memory, and the value for those types is retrieved every time by pointing to the point in memory for the most updated value stored there.
That takes us to how to copy values from reference types in an intentional way, and those are Value Copy and Reference Copy. By doing value copy, it means that the value stored in the memory of the original data is copied to another point in memory, while a reference copy means that the copy of the original data is made by simply pointing a new variable to the same place in memory where the original data is pointing to in memory. The implication of either approach is that in the case of value copy, modifying the value-copied data will not mutate the original data, in the case of reference copy, however, modifying the reference-copied data will mutate the original data since that will mutate the data stored in the same memory location that both the original data and reference-copied data are pointing to.
The trickier part is in when you have reference types stored within reference types as is the case of the data parameter that your code shows have a section property, whose value is an Array (a reference type), that is a collection of item objects, each of which is an Object (also a reference type). The trick here is that even though Array.prototype.map method is known to be a pure function that makes a value copy of the array it is operating upon; which is section in your case, yet, it will only make a value copy of the array and cares less about making a value copy of the items within the array, which is what caused how mutating those items ended up mutating the original data you passed as an argument into the function eventually.
Thus, the way to solve that is to make a deep value copy of your arguments before operating on them within your function once they are reference types, and making a value copy of their children that are also reference types.
The below approach is one of many approaches that can achieve that:
const pureMassageData = (data) => {
let dataCopy = { ...data };
// Make a value copy of other reference props as is done for sections and then items within section as below
dataCopy.sections = dataCopy.sections.map(item => ({ ...item }));
return {
title: dataCopy?.label,
sections: dataCopy?.sections?.map((item) => {
if (item.display === "HR") {
const tableBody = [{ id: "new" }];
item["tableData"] = tableBody;
}
return item;
}),
}
}
I am having trouble maintaining the original value of a variable after making new changes to the original variable.
Code:
(...)
data = Illumination.calculate_N(data)
data = Illumination.calculate_pi(data)
data = Illumination.calculate_kwh(data)
data = Illumination.calculate_ca(data)
let data_base = data
let ca_base = data.ca
let kwh_base = data.kwh
let pi_base = data.pi
(...)
data = Illumination.calculate_N(data)
data = Illumination.calculate_pi(data)
data = Illumination.calculate_kwh(data)
data = Illumination.calculate_ca(data)
let data_proposto = data
let ca_proposto = data.ca
let kwh_proposto = data.kwh
let pi_proposto = data.pi
-----------------------------------
EXAMPLE:
static calculate_ai(data){
data.ai = data.areaTotal*data.au
return data
}
It was expected that the original variable (date) would have its values changed, and this happens correctly, however, the variables data_base and data_proposto are not keeping their values
Both variables at the end of the calculation have the same values as the variable date
The variables ca_proposto, ca_base, and the like store their values correctly
Any idea?
The only interactions of the variables data_base and data_proposto were their creations with the data variable and their return of the function
OBS: If I use console.log () to view the value of the data_base variable before redoing the new calculations (Illumination.calculate_N (data)), the value of the variable appears correctly as it should, it is changed shortly after these calculations.
Because in both cases you are assigning not the object itself in the current state, but a reference to that object. What you need to do is to clone the object so the state is frozen at that point.
Simple Clone (Shallow Copy)
let data_base = Object.assign({}, data); //you get a clone of data
let data_proposto = Object.assign({}, data);
The limitation here is that it only does a shallow copy. See Deep Copy below for further explanation.
JSON Clone
This is a quick-and-dirty way to clone as it converts a JSON object to a string, and then back. i.e. you are no longer getting a reference, but a new object.
let data_base = JSON.parse(JSON.stringify(data));
let data_postero = JSON.parse(JSON.stringify(data));
But this won't work if your object is not JSON-safe.
Deep Copy
The least elegant method is probably safest. It deep copies the properties over into a new object. The key difference with Object.assign() is that it copies the values of nested properties, whereas Object.assign() copies the reference to nested objects.
So with Object.assign() any subsequent changes in your nested objects will affect all versions of your "clones". This won't happen if your clones only have property values of those nested objects at the time of cloning – these values are not affected by any changes to the nested objects.
const deepCopy = function(src) {
let target = {};
// using for/in on object also returns prototype properties
for (let prop in src) {
// .hasOwnProperty() filters out these prototype properties.
if (src.hasOwnProperty(prop)) {
target[prop] = src[prop]; //iteratively copies over values, not references
}
}
return target;
}
let data_base = deepCopy(data);
let data_postero = deepCopy(data);
#chatnoir Defined the problem very well, But I do not agree with his JSON serialization solution due to the below probleam:
You will lose any Javascript property that has no equivalent type in
JSON, like Function or Infinity. Any property that’s assigned to
undefined will be ignored by JSON.stringify, causing them to be missed
on the cloned object.
My suggestion to perform deep copy is to rely on a library that’s well
tested, very popular and carefully maintained: Lodash.
Lodash offers the very convenient clone and deepclone functions to perform shallow and deep cloning.
Lodash has this nice feature: you can import single functions separately in your project to reduce a lot the size of the dependency.
Please find the running sample code here: https://glitch.com/edit/#!/flavio-lodash-clone-shallow-deep?path=server.js:1:0
You are using the same variable data inside and outside functions.
ie; data is in the global scope.
static calculate_ai(data){
data.ai = data.areaTotal*data.au
return data
}
even though you are expecting the scope of the variable data inside the method calculate_ai to be limited to that method, it is not the case. data is in global scope and therefore, the value changes inside the method for the variable affects outside as well.
An effective solution is to use a different variable inside the method.
A variable is like an octopus tentacle, and not as a box (as it’s commonly described). In this analogy, the variable's name can be thought of as the name of a tentacle.
A variable (tentacle) holds on to a value in what’s called a binding. A binding is an association of a variable to a value: x = 1.
In JavaScript, if a variable b holds on to variable a, changing the value to which variable a holds onto, will change the value to which variable b holds onto, as b and a are referencing to the same value:
let a = {key: 1}
let b = a
console.log(`a: ${a.key}`) // -> 1
console.log(`b: ${b.key}`) // -> 1
a.key = 2
console.log(`a: ${a.key}`) // -> 2
console.log(`b: ${b.key}`) // -> 2
a = {key: 3} // This will point variable 'a' to a new object, while variable 'b' still points to the original object.
console.log(`a: ${a.key}`) // -> 3
console.log(`b: ${b.key}`) // -> 2
I have a two-part issue.
1) I need to access a list of unique values nested within a complicated object. I am able to output the object via a console.log as such:
console.log(dataStore);
This outputs the following (in part):
`Object
getResponses: function getResponses(ids)
arguments: null
caller: null
length: 1
name: "getResponses"
prototype: Object
__proto__: function ()
<function scope>
Closure
responseCache: Object
12345: gr.Response
12346: gr.Response
etc...
getImg: function (imageId)
etc... `
I need a call that gathers the list of numeric values under responseCache: 12345, 12346, 12347, etc. The total amount of these values may very. Could be 10, could be 100 in the list.
2) I need to create a conditional statement that compares the value for a variable that is not a part of the object above. For example:
variableX = variableXvalue;
if ("variableXvalue" is one of the unique values in "responseCache") {
//then do this
}
Not sure if it matters for the purposes of the question here, but the variableX is in a loop that iterates through attributes on a page. It loops through all matching tags/attributes on a page, and needs to compare those attribute values with the full list of responseCache values each time it loops.
Thank you for considering this question.
Let's look at an example of how this sort of code could be written.
var obj = (function() {
var responseCache = {
12345: 12,
12348: 34,
12355: 56
};
return {
getResponses: function getResponses(ids) {
return responseCache[id];
}
};
})();
console.log(obj);
If you run that code in your console, you'll get able to drill down and see essentially the same output as what you're seeing. Now, how can you directly access responseCache? You can't. It's effectively private and only directly accessible from within that closure. Outside code cannot access that variable directly. You can only indirectly access it if any functions on obj use it.
I have a scoped variable that stores an archive:
viewScope.MY_SCOPE = new Array();
viewScope.MY_SCOPE.push(["id0", 0, true]);
viewScope.MY_SCOPE.push(["id1", 1, false]);
viewScope.MY_SCOPE.push(["id2", 3, true]);
now I want to update one of item.
viewScope.MY_SCOPE[1][2] = "true";
and this fails with the error:
Error while executing JavaScript action expression put(int index,FBSValue value) not supported in JavaWrapperObject.
How do I update a specific item in the array?
When adding the SSJS array object to the scope, it is converted to a java.util.Vector. Hence, if you want to set the value, you should use
viewScope.MY_SCOPE[1].set(2,"true");
instead of viewScope.MY_SCOPE[1][2] = "true";.
I think the problem is that using ...[2] = "true" tries to execute the put method of the given object. While put is available in Maps like HashMaps or the scope maps, Vectors use set instead of put to change values. For that reason, you get the "action expression put(...) not supported" error. In contrast to this, it's no problem to get the variable with viewScope.MY_SCOPE[1][2] because the get method is availiable in both HashMaps and Vectors.
When storing arrays in scope variables, I like to put the value into a properly typed javascript variable, make edits, and then replace the scope variable with the updated value.
In your case I would do the following:
var tempVar = viewScope.MY_SCOPE.toArray(); //convert to array to make sure properly typed
tempVar[1][2] = true;
viewScope.put(MY_SCOPE,tempVar);
Update:
After testing your code along with mine, I too get the same error. To be honest, I never would have messed with multi dimensional arrays in the first place. This is a perfect opportunity to use an array of objects:
var tempVar = []; // initialize the array
tempVar.push({val1:"id0",val2:0,val3:true});
tempVar.push({val1:"id1",val2:1,val3:false});
tempVar.push({val1:"id2",val2:3,val3:true});
viewScope.put("MY_SCOPE",tempVar);
Then to change the desired value:
var tempVar = [];
tempVar = viewScope.get("MY_SCOPE");
tempVar[1].val3 = true;
viewScope.put("MY_SCOPE",tempVar)
I tested this method and it works fine.
I'm experiencing an odd behavior (maybe it isn't odd at all but just me not understanding why) with an javascript array containing some objects.
Since I'm no javascript pro, there might very well be clear explanation as to why this is happening, I just don't know it.
I have javascript that is running in a document. It makes an array of objects similar to this:
var myArray = [{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...];
If I print out this array at the place it was created like JSON.stringify(myArray), I get what I was expecting:
[{"Id":"guid1","Name":"name1"},{"Id":"guid2","Name":"name2"},...]
However, if I try to access this array from a child document to this document (a document in a window opened by the first document) the array isn't an array any more.
So doing JSON.stringify(parent.opener.myArray) in the child document will result in the following:
{"0":{"Id":"guid1","Name":"name1"},"1":{"Id":"guid2","Name":"name2"},...}
And this was not what I was expecting - I was expecting to get the same as I did in teh parent document.
Can anyone explain to me why this is happening and how to fix it so that the array is still an array when addressed from a child window/document?
PS. the objects aren't added to the array as stated above, they are added like this:
function objTemp()
{
this.Id = '';
this.Name = '';
};
var myArray = [];
var obj = new ObjTemp();
obj.Id = 'guid1';
obj.Name = 'name1';
myArray[myArray.length] = obj;
If that makes any difference.
Any help would be much appreciated, both for fixing my problem but also for better understanding what is going on :)
The very last line might be causing the problem, have you tried replacing myArray[myArray.length] = obj; with myArray.push(obj);? Could be that, since you're creating a new index explicitly, the Array is turned into an object... though I'm just guessing here. Could you add the code used by the child document that retrieves myArray ?
Edit
Ignore the above, since it won't make any difference. Though, without wanting to boast, I was thinking along the right lines. My idea was that, by only using proprietary array methods, the interpreter would see that as clues as to the type of myArray. The thing is: myArray is an array, as far as the parent document is concerned, but since you're passing the Array from one document to another, here's what happens:
An array is an object, complete with it's own prototype and methods. By passing it to another document, you're passing the entire Array object (value and prototype) as one object to the child document. In passing the variable between documents, you're effectively creating a copy of the variable (the only time JavaScript copies the values of a var). Since an array is an object, all of its properties (and prototype methods/properties) are copied to a 'nameless' instance of the Object object. Something along the lines of var copy = new Object(toCopy.constructor(toCopy.valueOf())); is happening... the easiest way around this, IMO, is to stringency the array withing the parent context, because there, the interpreter knows it's an array:
//parent document
function getTheArray(){ return JSON.stringify(myArray);}
//child document:
myArray = JSON.parse(parent.getTheArray());
In this example, the var is stringified in the context that still treats myArray as a true JavaScript array, so the resulting string will be what you'd expect. In passing the JSON encoded string from one document to another, it will remain unchanged and therefore the JSON.parse() will give you an exact copy of the myArray variable.
Note that this is just another wild stab in the dark, but I have given it a bit more thought, now. If I'm wrong about this, feel free to correct me... I'm always happy to learn. If this turns out to be true, let me know, too, as this will undoubtedly prove a pitfall for me sooner or later
Check out the end of this article http://www.karmagination.com/blog/2009/07/29/javascript-kung-fu-object-array-and-literals/ for an example of this behavior and explanation.
Basically it comes down to Array being a native type and each frame having its own set of natives and variables.
From the article:
// in parent window
var a = [];
var b = {};
//inside the iframe
console.log(parent.window.a); // returns array
console.log(parent.window.b); // returns object
alert(parent.window.a instanceof Array); // false
alert(parent.window.b instanceof Object); // false
alert(parent.window.a.constructor === Array); // false
alert(parent.window.b.constructor === Object); // false
Your call to JSON.stringify actually executes the following check (from the json.js source), which seems to be failing to specify it as an Array:
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
//stringify