Performance of mutating object versus shallow clone - javascript

I like the functional programming paradigm which according to me produces cleaner code and easier to understand, but I'd like to know if the performance loss is significative or not.
Let's take the example of a function that adds a new property to an object. A non-functional approach would look like this:
const addProp = (obj, prop, val) => {
obj[prop] = val; // Mutate the object
return obj;
}
While the functional approach would look like this:
const addProp = (obj, prop, val) => ({ ...obj, [prop]: val }); // Shallow clone
What's the cost of the shallow clone compared to the object mutation? I'd like to know how much functional programming patterns degrade performance (if they do).

The performance of making shallow copies is much worse than the performance of mutation. If I have an array with 400 elements and I make a shallow copy, that's going to be much more expensive than destructively modifying a single element of the array. And the longer the array is, the worse the gap becomes. The same problem occurs with an object with many properties.
One essential element of efficient functional programming is using the right data structures. In this case, you want a data structure where modifying part of the data structure does not mean you have to completely recreate the data structure. You want a local modification of a data structure to be able to share most of its memory with the original.
An extremely basic example of this is a singly linked list. If you wish to prepend a single element to a linked list, this is an O(1) operation because the new list shares most of its memory with the old list. However, adding a single element to an array without mutating the original array is a linear-time operation because the whole array must be copied.
For sequences and maps, one generally ends up using some sort of balanced tree. Functional programming languages include these data structures as part of their standard libraries, but I'm sure someone has written a library for functional 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.

How do I deep clone an object in React?

let oldMessages = Object.assign({}, this.state.messages);
// this.state.messages[0].id = 718
console.log(oldMessages[0].id);
// Prints 718
oldMessages[0].id = 123;
console.log(this.state.messages[0].id);
// Prints 123
How can I prevent oldMessages to be a reference, I want to change the value of oldMessages without changing the value of state.messages
You need to make a deep copy. Lodash's cloneDeep makes this easy:
import cloneDeep from 'lodash/cloneDeep';
const oldMessages = cloneDeep(this.state.messages);
oldMessages[0].id = 123;
First let's clarify the difference between shallow and deep clone:
A shallow clone is a clone that has its primitive properties cloned but his REFERENCE properties still reference the original.
Allow me to clarify:
let original = {
foo: "brlja",
howBigIsUniverse: Infinity,
mrMethodLookAtMe: () => "they call me mr. Method",
moo: {
moo: "MOO"
}
};
// shallow copy
let shallow = Object.assign({}, original);
console.log(original, shallow); // looks OK
shallow.moo.moo = "NOT MOO";
console.log(original, shallow); // changing the copy changed the original
Notice how changing the shallow copy's not primitive property's inner properties REFLECTED on the original object.
So why would we use shallow copy?
It is definitely FASTER.
It can be done in pure JS via 1 liner.
When would you use shallow copy?
All of your object's properties are primitives
You are making a partial copy where all your copied properties are primitives
You don't care about the fate of the original (is there a reason to copy and not use that one instead?)
Oke, let's get into making a propper (deep) copy. A deep copy should obviously have the original object coped into the clone by value, not references. And this should persist as we drill deeper into the object. So if we got X levels deep nested object inside of the original's property it should still be a copy not a reference to the same thing in memory.
What most people suggest is to abuse the JSON API. They think that turning an object into a string then back into an object via it will make a deep copy. Well, yes and NO. Let's attempt to do just that.
Extend our original example with:
let falseDeep = JSON.parse(JSON.stringify(original));
falseDeep.moo.moo = "HEY I CAN MOO AGAIN";
console.log(original, falseDeep); // moo.moo is decoupled
Seems ok, right? WRONG!
Take a look at what happened to the mrMethodLookAtMe and howBigIsUniverse properties that I sneaked in from the start :)
One gives back null which is definitely not Infinity and the other one is GONE. Well, that is no bueno.
In short: There are problems with "smarter" values like NaN or Infinity that get turned to null by JSON API. There are FURTHER problems if you use:
methods, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays as your original object's properties.
Why? Well this produces some of the nastiest to track bugs out there.. I have nightmares tracking the disappearing methods or type being turned to another (which passed someone's bad input parameter check but then couldn't produce a valid result) before Typescript became a thing.
Time to wrap this up! So what is the correct answer?
You write your own implementation of a deep copy. I like you but please don't do this when we have a deadline to meet.
Use a deep cloning function provided to you by the library or framework you already use in the project.
Lodash's cloneDeep
Many people still use jQuery. So in our example (please put import where it belongs, on top of the file):
import jQ from "jquery";
let trueDeep = jQ.extend(true, original, {});
console.log(original, trueDeep);
This works, it makes a nice deep copy and is a one-liner. But we had to import the entire jQuery. Which is fine if it is already being used in project, but I tend to avoid it since it is over-bloated and has terribly inconsistent naming.
Similarly, AngularJS users can use angular.copy().
But what if my framework/library does not have a similar function?
You can use my personal SUPERSTAR among JS libraries (I am not involved in the project, just a big fan) - Lodash (or _ for friends).
So extend our example with (again, mind the position of import):
import _ from "lodash"; // cool kids know _ is low-dash
var fastAndDeepCopy = _.cloneDeep(objects);
console.log(original, lodashDeep);
It is a simple oneliner, it works, it is fast.
This is pretty much it :)
Now you know the difference between shallow and deep copy in JS. You realize JSON API abuse is just that, abuse and not a true solution. If you are using jQuery or AngularJS already you now know there is a solution already there for you. If not you can write your own or consider using lodash.
The entire example can be found here:
codesandbox - entire example
Try Using
let tempVar = JSON.parse(JSON.stringify(this.state.statename))
What actually you are doing
let oldMessages = Object.assign({}, this.state.messages);
is a shallow copy which is similar to {...this.state.message} with spread operator.
Object has its own reference in memory to destroy it you can use JSON.parse (JSON.stringify(object))
no matter how nested key it has, it will remove the reference of the object and you will get a new object.
This concept is called a deep copy or deep clone.

Destructuring ImmutableJS Map for React Component Props

We have a project using React + Redux + ImmutableJS. One of our engineers recently added a helper method to support destructuring an ImmutableJS Map when passing it to a component as props:
export function toObjectShallow(mapping: Map) {
const result = {};
mapping.map((value, key) => {
result[key] = value;
});
return result;
}
Thus, we can still do the following and avoid being verbose with repeated calls to Map.get:
<XyzComponent {...toObjectShallow(this.props.xyz)}/>
Yes, that's essentially making two shallow copies (our method + destructuring) of the original object. That should be of minimal expense. I'm wondering though, since I don't see this kind of recommendation really anywhere else in the React/Redux/Immutable communities, is there something else I'm missing that would make this unideal?
The passed properties are still the original, immutable props. It's just the containing object is mutated which doesn't matter, because it's not getting passed to the component anyways. So, what gives? This seems like such a simple solution while avoiding toJS(). Why isn't it really mentioned anywhere?
I followed the advice in the redux docs to use a HOC for all connected components to allow me to interact with plain javascript objects outside of redux. So my selectors and reducers still use ImmutableJS objects, but the rest of the code uses plain javascript objects:
https://redux.js.org/docs/recipes/UsingImmutableJS.html#use-a-higher-order-component-to-convert-your-smart-components-immutablejs-props-to-your-dumb-components-javascript-props
edit- not sure if this is the toJS you are mentioning above, I had assumed you meant ImmutableJS.toJS.
as far as preferences, using an HOC you only need to do it once per component, as opposed to each time you use a component in your method.
I think the "correct" answer I was looking for was to continue using Immutable JS's toObject() which does a shallow conversion of an Immutable Map object to a regular object, thus allowing us to continue using syntactic sugar while keeping the properties immutable and not having to use our own shallow copy.

Clone a new object simply by assigning object to a variable using Immutable.js

I'm looking at the documentation for Immutable.js, specifically the following:
var map1 = Immutable.Map({a:1, b:2, c:3});
var clone = map1;
but I'm confused as to how simply assigning map1 to clone creates a clone rather than a reference?
Update:
The docs state "If an object is immutable, it can be "copied" simply by making another reference to it instead of copying the entire object. Because a reference is much smaller than the object itself, this results in memory savings and a potential boost in execution speed for programs which rely on copies (such as an undo-stack)."
I just tested this in a jsbin though, and clone does === map1. I think their use of the word 'clone' in the docs is a little misleading.
Since Immutable.Map is immutable, the notion of cloning is obsolete. Their point is that you don't have to bother about cloning or not, it doesn't matter.
The docs are indeed confusing, and indeed it is a reference not a clone. The effect of cloning would be the same anyways.

When/why to use map/reduce over for loops

So I am getting into a bit of object manipulation in JavaScript for the first time and I have a question I'm wondering if anyone could answer.
When I have an object I want to manipulate I could do something to the extent of a few nested for loops, however there are functions built into JavaScript, like map/reduce/filter, and libraries like lodash/underscore.
I assume the latter (map/reduce/filter and the libraries) are better practice but I'm just curious as to why.
I am doing some pretty basic object manipulation that could be solved with a few well placed for loops to grab and change the right keys/values in the object, but can be easily done with the functions/libraries in JS. Just curious as to how they are better - like better performance/cleaner code/ease of use/whatever else.
Apologies, there is no code. I would very much appreciate anyone helping me understand more here.
Edit - so taking from the examples for map()
I could take the example for javascript.map
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
var reformattedArray = kvArray.map(function(obj){
var rObj = {};
rObj[obj.key] = obj.value;
return rObj;
});
I could do something like
var kvArray = [{key:1, value:10}, {key:2, value:20}, {key:3, value: 30}];
var reformattedArray = [];
for(var object in kvArray){
//combine both values into object inside of kvArray[object]);
};
A lot less code - but any other benefits worth knowing about?
I know I'm replying to an old answer but just wanted to point out for future readers.
Map reduce and filter functions come from the functional programming world.
These are first class built-in operators in languages like Lisp, Haskell, and others(ml?).
Functional languages tend to prefer to run operators over immutable data than make the code run over the data to operate on it (say loops).
So they provide simpler but powerful interfaces like map, filter and reduce when compared to providing for and while loops.
It also helps them satisfy other requirements like immutability etc. That's why maps give u back a new map instead of mutating the old one. These are very good from a concurrency point of view, though they may be slower in certain contexts.
This approach usually leads to fewer errors in code in multi-threaded or high concurrency apps.
When multiple actors act on the same piece of data, immutability helps keep code from stepping on each other's toes.
Since javascript tries to be partially functional by providing some functionalities of functional programming languages, it might have made sense to implement map, filter and reduce functions in it too.
YMMV depending on what you are doing with the tools you are given.
If your code works better with a for loop, go for it.
But if you ever find asynchronous code munching on common data and you end up splitting your hairs trying to debug a loop.
Say hi, to map, reduce and filter.
.map() allows you to create a new array by iterating over the original array and allowing you to run some sort of custom conversion function. The output from .map() is a new array.
var orig = [1,2,3,4,5];
var squares = orig.map(function(val) {
return val * val;
});
console.log(squares); // [1,4,9,16,25]
.reduce() allows you to iterate over an array accumulating a single result or object.
var orig = [1,2,3,4,5];
var sum = orig.reduce(function(cum, val) {
return cum + val;
}, 0);
console.log(sum); // 15
These are specialized iterators. You can use them when this type of output is exactly what you want. They are less flexible than a for loop (for example, you can't stop the iteration in the middle like you can with a for loop), but they are less typing for specific types of operations and for people that know them, they are likely a little easier to see the code's intent.
I have not myself tested the performance of .map() and .reduce() versus a for loop, but have seen tests for .forEach() which showed that .forEach() was actually slower in some browsers. This is perhaps because each iteration of the loop with .forEach() has to call your callback function, whereas in a plain for loop, you do not have to make such a function call (the code can be directly embedded there). In any case, it is rare that this type of performance difference is actually meaningful and you should generally use whichever construct makes clearer, easier to maintain code.
If you really wanted to optimize performance, you would have to write your own test case in a tool like jsperf and then run it in multiple browsers to see which way of doing things was best for your particular situation.
Another advantage of a plain for loop is that it can be used with array-like objects that support indexing, but do not support .reduce() and .map().
And, a for/of loop can be used with any object that implements the iterator protocol such as HTMLCollection.
This is like asking if I like basketball or football better. Both have their positives.
If you have 10 developers look at your for loop, 9 out of 10 will know what you are doing right away. Maybe half will have to look up what the map() method is, but then they'll also know what's going on. So in this respect, a for loop is easier for others to read.
On the flip side, map() will save you two or three lines of code.
As far as performance goes, you'll find map() is built internally with something akin to a for loop. You might see a few milliseconds of difference when it comes to performance speeds if you run them through large iterations; but they'll never be recognizable to an end user.
forEach(): Executes a provided function(callback) once for each array element. Doesn’t return anything (undefined) but this callback is allowed to mutate the calling array.
map(): Executes a provided function(callback) once for each array element and creates a new array with the results of this execution. It cannot mutate the calling array content.
Conclusion
Use map() when you need to return a new array.
Use forEach() when you want to change the original array
Use for when you need more control over the iteration (eg: you want to iterate every three elements (i + 3))
Bumped into this while searching for something else. So trying to answer it even if it is a old thread as the concepts applies no matter what.
If you consider performance and flexibility, "for" loop always beats the others, just because it doesn't have the overhead of calling a function for each iteration and can be used for any purpose.
But, there are other gains with functions like forEach, map, reduce etc (let's call them functional methods). It is mainly the readability, maintainability.
Below are few drawbacks of for loop
Introduces new variables to the scope, just for the counter/iteration
Hard to debug errors, due to un-intentional changes to counter variables. This becomes more difficult and the chances to make a mistake increases more as the number of loops with in loops increase
Developers have the habit of using loop variables as i, j, k. It is very easy to lose the track of which counter and which inner loop the code is executing once the loop increases certain lines of code
With ES6, we have at least limited/local scope introduced by 'let'. But before, the variables introduced by for loop have a function scope causing even more accidental errors
To avoid all of these, it is suggested to use functions like forEach, map, reduce when you know what you have to do (Not to forget most of these functional methods offer immutability). A small sacrifice in terms of performance for the greater good and more succinct code.
With ES6, most of the functional methods are supported by the language itself. They are optimised and we don't have to rely on libraries like lodash (unless there is a severe performance gain).
Just bumped into this and found that none of the answers highlights one important difference between for-loop and map as to when to use one over the other:
With map you can't break out of an iteration which you can with for-loop.
For e.g, you can't do this
const arr = [5, 6, 9, 4];
arr.map(elem=>{
if(elem === 5){
break; //This is not allowed
}
})
Summarising the differences between higher-order array methods (map, reduce, filter, etc. - I'll refer to these as HOMs) vs for loops, and including a few other points:
Counter variables: for loops introduce variables that introduce errors such as: OBOE; block scoping errors (which are complicated further by the differences in let and var declaration scoping)
Availability: HOMs are only available to objects that are arrays (Array.isArray(obj)); for loops can be used on objects that implement the iterator protocol (which includes arrays),
Early execution exit: there is none for HOMs; loops have the break and return statement for this,
Consecutive async iteration execution: Not possible in HOMs. Notice that only a loop can delay between console logging executions:
// Delays for a number of milliseconds
const delay = (ms = 1000) => new Promise(resolve => setTimeout(resolve, ms));
const items = ['a', 'b', 'c'];
const printItem = async (item) => {
await delay();
console.log(item);
}
const testForLoopParallelism = async () => {
for (const item of items) {
await printItem(item);
}
};
const testHigherOrderParallelism = () => {
return Promise.all(items.map(async item => await printItem(item)));
}
const run = async () => {
// Prints consecutively at a rate of ~1s, for a total of ~3s
console.time('for');
await testForLoopParallelism();
console.timeEnd('for');
// Prints all concurrently, for a total of ~1s
console.time('HOM');
await testHigherOrderParallelism();
console.timeEnd('HOM');
};
run();
Less importantly but worth noting:
Verbosity: array HOMs may be shorter than for loops
Subjectively easier to read: this can be argued for both HOMs and for for ... in loops
Subjectively better understood: loops may be more well known than HOMs for Javascript newcomers
Performance: May have performance differences that may need to be considered in high performance codebases on a case-by-case basis
Immutability aid: May aid in providing immutability - although it is possible to create mutations using either HOMs or for loops, e.g.,
items.map((item) => {
items.push(item);
return `${item}x`;
});
map, reduce, etc are functions that container-like data-structures should implement so that consumers can make use of them without having to understand their internals. These functions accept your logic as input. This allows you to implement changes to the internals of those data-structures without affecting their consumers.
The real reason why map is superior to for loops is because they are much easier to develop with as your application evolves. What if your requirements change such that you now have an object?
import map from 'lodash/fp/map';
import mapValues from 'lodash/fp/mapValues';
const before = map(logic, data);
const after = mapValues(logic, data);
And again, what if your requirements change such that now you have a tree? Well, now you're a good developer that realises that the code that is responsible for traversing the tree should be separated from the business logic. It should be written once, thoroughly tested once, be maintained by the team that owns the data-structure, and accept business logic as input. All so that consumers do not have to worry about its internals.
Instead, consumers should just do this.
import { mapTreeValues } from 'tree'
const before = map(logic, data);
const after = mapTreeValues(logic, data);
Long story short, for should only ever be used by owners of data structures to implement functions such as map, reduce, etc. These functions should never be coupled to business logic, instead they should accept it as input. Remember the single responsibility principle?
Aside: To preempt comparison with custom iterables, there are benefits to these being higher-order functions to improve ease of composition. For example:
getUser().then(updateUser)
getUsers().then(map(updateUser))

Categories

Resources