About immutable update pattern of objects: newObject=JSON.parse(JSON.stringify(object))? - javascript

demo here:
let objectTest={
a:"one",
b:"two",
c:"three"
}
let newObject = JSON.parse(JSON.stringify(objectTest))
console.log("hello, I am a new object: ", newObject)
console.log("newObject === objectTest: ", newObject === objectTest)
Is it okay to make an immutable copy of an object like that: newObject=JSON.parse(JSON.stringify(object))?
Just to grab the main properties of the object -the one that appears on the console.log().
Can we consider this transformation an immutable one?
From my demo I would say yes,since it seem to really create a new object.

Yes, it is; JSON.parse creates a new object every time

Related

How to copy json object without reference in vue?

In my component i have declarated some data like this:
data() {
return {
defaultValue: {json object with some structure},
activeValue: {}
...
And in component methods a make copy this value:
this.activeValue = this.defaultValue
But problem is, after change this.activeValue value a have changes in this.defaultValue too.
If i use Object.freeze(this.defaultValue) and trying change this.activeValue i have get error - object is not writable.
How i can make copy of data but without reference?
If you have simple object, quickest and easiest way is to just use JSON.parse and JSON.stringify;
const obj = {};
const objNoReference = JSON.parse(JSON.stringify(obj));
this.activeValue = { ...this.defaultValue }
Using an ES6 spread operator will help you to do a copy if you do not have a nested object. If you equate using equal = sign, it will not create a new object, it will just create a variable with the reference to the current object (like a shallow copy).
To do a complete deep copy, even it is nested object, go for this:
const objNoReference = JSON.parse(JSON.stringify(obj));
as suggested by Owl.
Click to read more for better understanding of the concept
A nicer way rather than using JSON.parse, JSON.stringify is:
this.activeValue = {...this.defaultValue}
but this is not natively supported by some browser (IE), unless used with a transpiler (babel)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Update
Considering your originial question is about a way in Vue, there is also a native method in vue:
this.activeValue = Vue.util.extend({}, this.defaultValue)
as for this answer.
Hope this helps!
Objects are assigned and copied by reference.
All operations via copied references (like adding/removing properties) are performed on the same single object.
To make a “real copy” (a clone) we can use Object.assign for the so-called “shallow copy” (nested objects are copied by reference).
For “deep cloning” use _.cloneDeep(obj) from loadash library.
JSON stringify&parse method have some issues like converting date objects to strings. It also cannot handle special data types like Map,Set,function etc... This is prone to future bugs.
I use the following method to deep copy an object.
REMEMBER! this is not a complete application of cloning. There are more data types to handle like Blob, RegExp etc...
const deepClone = (inObject) => {
let outObject, value, key
if (typeof inObject !== "object" || inObject === null)
return inObject
if (inObject instanceof Map) {
outObject = new Map(inObject);
for ([key, value] of outObject)
outObject.set(key, deepClone(value))
} else if (inObject instanceof Set) {
outObject = new Set();
for (value of inObject)
outObject.add(deepClone(value))
} else if (inObject instanceof Date) {
outObject = new Date(+inObject)
} else {
outObject = Array.isArray(inObject) ? [] : {}
for (key in inObject) {
value = inObject[key]
outObject[key] = deepClone(value)
}
}
return outObject
}
You can use 'JSON.parse and stringify' or using some clone function in the libs like lodash (underscore, ramda...)
Also a simple solution is to store defaultValue: {json object with some structure} with JSON.stringify(defaultValue) in a string variable:
var x = JSON.stringify(this.defaultValue);
If you need it as JSON object again you can get it with JSON.parse():
var newObject = JSON.parse(x);
The object reference is also broken doing it this way, x will stay unchanged if the content of the object defaultValue is altered.

JavaScript Pass Argument to Function keeping Argument Data Intact

I'm having a strange situation in JavaScript where I am creating an Object and then passing it as an Argument to a Function which then updates the values and then returns a new updated Object.
function objBuild() {
var obj_old = {}; // original object
var obj_new = {}; // updated object
// set values for original object
obj_old.val01 = "val01_old";
obj_old.val02 = "val02_old";
obj_old.val03 = "val03_old";
// set values for new object using
// the original object as a template
obj_new = objUpdate(obj_old);
console.log(obj_old); // this shows obj_new data which I don't want
console.log(obj_new); // this shows obj_new data which I want
}
function objUpdate(obj) {
obj.val03 = "val03_new";
return obj;
}
I was expecting the new Object to be updated with the new Value, which it is, however the old Object is updated as well.
Maybe the function is taking the old Object as a reference and even though I'm returning a separate value it remembers what's happened to it?
I'm not sure but if that is the case is it possible to keep the old Object intact?
Please read: Is JavaScript a pass-by-reference or pass-by-value language?
In javascript, objects are "technically" passed by "reference". Hence, the original value is altered, because obj is, in fact, the original object.
You need to clone the object, and there is a vast scenario about "how" you should do that, since object may be shallow copied or deep copied. Read more about that here: What is the most efficient way to deep clone an object in JavaScript?
and here: How do I correctly clone a JavaScript object?
Anyway, to fix your issue, just use Object.assign on a brand new object to copy the values of the original object, for your case, it's just enough, despite I recommend you to read the above posts to learn when and how to properly copy objects.
function objBuild() {
var obj_old = {}; // original object
var obj_new = {}; // updated object
// set values for original object
obj_old.val01 = "val01_old";
obj_old.val02 = "val02_old";
obj_old.val03 = "val03_old";
// set values for new object using
// the original object as a template
obj_new = objUpdate(obj_old);
console.log(obj_old); // this shows obj_new data which I don't want
console.log(obj_new); // this shows obj_new data which I want
}
function objUpdate(obj) {
var _cloned = Object.assign({}, obj);
_cloned.val03 = "val03_new";
return _cloned;
}
objBuild();
You're not actually creating a new object, you're setting the old object to the new object. So you're correct, the old object value is still being referenced.
If you want to make a new object, and you don't have any nested objects, I would use Object.assign(). This makes a shallow copy of the object. You could do something like this:
obj_new = objUpdate(Object.assign({}, obj_old));
This will create a new object with the enumerable properties of the old object.
If you have nested objects, these will still be copied by reference, so I would loop over the object and copy the properties that way.
Remember that objects, including arrays are passed by reference while strings, booleans and numbers are passed by value.
Here, you are passing object(by reference) so it is modifying the values of old object. Both old and new objects are pointing to the same value.
function objBuild() {
var obj_old = {}; // original object
var obj_new = {}; // updated object
// set values for original object
obj_old.val01 = "val01_old";
obj_old.val02 = "val02_old";
obj_old.val03 = "val03_old";
// set values for new object using
// the original object as a template
obj_new = objUpdate(obj_old);
console.log(obj_old); // this shows obj_new data which I don't want
console.log(obj_new); // this shows obj_new data which I want
}
function objUpdate(obj_old) {
var obj = JSON.parse(JSON.stringify(obj_old));
obj.val03 = "val03_new";
return obj;
}
objBuild();

Why does push of local array mutate global array?

I am puzzled why this following bit of code will return mutations of both the local and global array:
var globalarray = [1,2,3];
function test(){
let localarray = globalarray;
localarray.push(4);
console.log(localarray);
console.log(globalarray);
}
setInterval(test, 2000);
Returns:
[1,2,3,4] for both
My impression was that localarray would be a copy of globalarray. I saw another answer that said in order to make a copy of an array you need to use .slice().reverse(), which seems like a workaround. Why does it not just create a new local copy? Is there a simple and efficient way to make a local copy of a global array? Otherwise it seems like making multiple mutations to a global array is terrible for performance.
The reason for this in your code is because you are simply telling your test function to point to the globalarray with the = operator. This is because in JavaScript, variable assignments do not inherently "copy" objects into the new variables; this might seem confusing, so just think of the = operator as a sign that points your code to the location of an object.
The only times that the = operator is making new copies is when you are working with primitive types. In those cases, you cannot inherently change what those objects are, so = is sufficient to make a copy like you would expect.
The reason for the .slice().reverse() is to work around the problem you are seeing. Another way you could do this is by using let localarray = globalarray.map(e => e), or as samee suggested, by using let localarray = [...globalarray]. The .map operator takes the function given to it as the first argument and applies it to every element, and then it stores the result in a different array at another location that is not the same as globalarray.
Keep in mind that these methods, and any others that might be suggested to you, are shorthand ways of doing
let localarray = new Array(globalarray.length);
for (let i = 0; i < globalarray.length; i++) {
localarray[i] = globalarray[i];
}
// localarray can now be freely modified because it does not point to the same array as globalarray
Also keep in mind that if you need to also create copies of the elements inside of the arrays, then you will have to use more comprehensive copying code. There are libraries that can do this sort of heavy-duty copying if you really need it.
In JavaScript (as in many other languages), objects are passed by reference. Arrays are also passed by reference (because an array is actually a type of object). So when you say: let localarrray = globalarray, you are actually setting localarray to a pointer that resolves to globalarray.
There are several strategies for obtaining a true copy. If you're trying to get a fresh copy of the original array, the Array prototype function of .map() is one of the most targeted tools for the job. It would look like this:
let localarray = globalarray.map(element => element);
Simple way to clone an array is just
let localarray = globalarray.slice();
I do it a different way to deep cloning:
clone: function() {
var clone = undefined;
var instance = this;
if ( XScript.is.xobject(instance) ) {
clone = {};
for ( var prop in instance ) {
if ( instance.hasOwnProperty(prop) ) {
var p = instance[prop];
if ( XScript.is.xobject(p) ) p = p.clone();
clone[prop] = p;
}//END IF this
}//END FOR prop
return clone;
}//END IF xobject
if ( XScript.is.xarray(instance) ) {
clone = instance.slice(0);
return clone;
}
return clone;
}//END FUNCTION clone
This clone will require you attaching the clone object to the object prototype and check to see if its an array, object, or other. I am not changing the function to fit all your needs because one should learn to somewhat how to change it and what to do instead of copy pasta. Plus it is just an example.

array of custom object in javascript - eliminate empty occurrence

I have a custom object that I would like to create an array of. When creating my array it creates an occurrence with empty properties, I understand why, but I would like to avoid this. I realize I could just delete the occurrence with the empty properties, but is there a better way?
function FileToPassBack(path, originalFileName, modifiedDate, newFilename) {
if (!(this instanceof FileToPassBack)) {
return new FileToPassBack(namepath, originalFileName, modifiedDate, newFilename);
}
this.Path = path;
this.OriginalFileName = originalFileName;
this.ModifiedDate = modifiedDate;
this.NewFileName = newFilename;
}
function PostSelectedItems() {
var allFilesToPassBack = new Array(new FileToPassBack());
$('#fileNamesTable').find('tbody>tr:visible')
.each(function (index, element) {
var row = $(this);
if (row.find('input[type="checkbox"]').is(':checked'))
{
var path = row.find('.pathTDClass').html();
var originalFileName = row.find('.originalFileNameTDClass').html();
var modifiedDate = row.find('.modifiedDateTDClass').html();
var newFileName = row.find('input[class="newFileNameTDClass"]').val();
var currentFileToAdd = new FileToPassBack(path, originalFileName, modifiedDate, newFileName)
allFilesToPassBack.push(currentFileToAdd);
}
});
//post path, original file name, modified date, new file name
var objectAsJSON = JSON.stringify(allFilesToPassBack);
}
I am new to JS, so excuse me if I am way off track.
new Array(number)
Creating a new array with a number will initialize it with a certain number of empty elements:
var a = new Array(5);
// a = ['','','','',''];
thus when you push it will add a new entry
a.push("a");
// a = ['','','','','', 'a'];
The best practice is not to use new Array, instead use [] syntax as it is more performant.
var allFilesToPassBack = [];
JSON Stringify
although it works, you need to be aware that you are stringify-ing an array not a JSON in your example code.
Update
why [] is more performant than new Array() and considered a best practice.
When you create an array using
var a = [];
You're telling the interpreter to create a new runtime array. No extra processing necessary at all. Done.
If you use:
var a = new Array();
You're telling the interpreter, I want to call the constructor "Array" and generate an object. It then looks up through your execution context to find the constructor to call, and calls it, creating your array.
why its a best practice?
The new Array() doesn't add anything new compared to the literal syntax, it only wraps the array with an object wrapper that is not needed, only adding overhead of calling the Array constructor and creating an object wrapper around the array.
additionally defining a function Array() {} in the code, new Array() will start creating new instances from this function instead of creating an array, its an edge case, but if you need an array, just declare it with [].
You never need to use new Object() in JavaScript. Use the object
literal {} instead. Similarly, don’t use new Array(), use the array
literal [] instead. Arrays in JavaScript work nothing like the arrays
in Java, and use of the Java-like syntax will confuse you.
Do not use new Number, new String, or new Boolean. These forms produce
unnecessary object wrappers. Just use simple literals instead.
http://yuiblog.com/blog/2006/11/13/javascript-we-hardly-new-ya/
There are ton of sources, but i'll keep it minimal for this update. new was introduced to the language as it is "familiar" to other languages, as a prototypal language, the new syntax is useless and wrong, instead the language should have Object.create. but thats a different topic, you can read more about it from experts like Kyle Simpson or Douglas Crockford.
I think, though am unsure what your actual problem is, that you are calling var allFilesToPassBack = new Array(new FileToPassBack()); to initialize your array? You don't need to do that.
Try replacing that line with this instead.
var allFilesToPassBack = []
That will init an empty array than you can then pushto in your jquery each loop.

How can do this function pure

I want to map an array, returning the same array except .created property, that I'm transforming from milliseconds to Date Object.
const posts = data.map((post) => {
post.created = new Date(post.created); //milliseconds to Date Object
return post;
})
This function do the result that I expected, but I don't like because the function inside is not pure, is mutating the object. How can be an alternative to do the same with pure function?
Thank you so much.
You can achieve this using Object.assign (docs) inside the map:
const posts = data.map((post) => {
return Object.assign({}, post, { created: new Date(post.created) });
})
This essentially clones the original post and then overrides the created property.
I would provide both solutions.
Clone the array and its objects
data.map(post => Object.assign({created: new Date(post.created)},post));
The first argument of Object.assign is the target object. It's also the object that Object.assign returns.
Modify the existing array
for(let post of data){
post.created = new Date(post.created);
}
This method is, of course, faster and costs less resources since it doesn't need to initialize a new array and its objects then copy all objects' properties like the other does.
The choice is yours. Just make sure you know what you're doing.
You can make copy without copying all of the attributes from the old object:
const posts = data.map((post) => {
const obj = Object.create(post);
obj.created = new Date(post.created); // shadow created with the new value
return obj;
});
In practice it uses JavaScript inheritance scheme so that the new object only has created as it's own attribute and the rest is inherited from the original object.

Categories

Resources