Is there a specific reason why is better to use .map than for loops in React?
I'm working on a project where all arrays are being iterated with for loops but I'm convinced that is better and good practice to use .map because it creates a copy of the array, that to my understanding is better practice but I can't find a specific reason.
Is there a specific reason why is better to use .map than for loops in React?
If you're just iterating, map is the wrong tool. map is for mapping arrays: producing a new array based on the values from the previous array. Someone somewhere is teaching map as an iteration tool, unfortunately, doing their students a disservice. (I wish I knew who it was so I could have a word.) Never do this:
// Don't do this
myArray.map(entry => {
// ...do something with `entry`...
});
For iteration, it's a choice between a for loop, a for-of loop, and the forEach method. (Well, and a few other things; see my answer here for a thorough rundown.)
For instance, using forEach
myArray.forEach(entry => {
// ...do something with `entry`...
});
vs. using for-of:
for (const entry of myArray) {
// ...do something with `entry`...
}
(Those aren't quite equivalent. The former has to be an array. The latter can be any iterable object.)
The reason you may see map a lot in React is that you're frequently mapping things in React, in at least two ways:
Mapping from raw data to JSX elements, like this:
return (
<div>
{myData.map(({id, name}) => <div key={id}>{name}</div>)}
</div>
);
Since that's a mapping operation, with the array created by map being used to provide the contents of the outer div, map is the right choice there.
Mapping from old state to new state, like this:
const [myArray, setMyArray] = useState([]);
// ...
setMyArray(myArray.map(obj => {...obj, update: "updated value"}));
Since that again is a mapping operation, creating a new array to set as the myArray state member, map is the right choice.
...but I'm convinced that is better and good practice to use .map because it creates a copy of the array...
If you want a copy/updated version of the array, yes, map is a good choice. It's more concise than the equivalent for loop (or even for-of):
const newArray = oldArray.map(entry => /*...update...*/);
vs.
// Probably not best practice unless you have conditional logic
// in the loop body that may or may not `push` (or similar)
const newArray = [];
for (const entry of oldArray) {
newArray.push(/*...update...*/);
}
.map() maps each array value to a new value, and returns a brand new array.
In React.js context, .map() can be used to map each array item to a piece of JSX fragment.
for loop also iterates over an array, just like .map(). The major difference is that you can specify custom computation with for loop. Whereas .map() is specifically designed for mapping
I'm trying to replace array elements (generic objects) with their alternatives (specific Objects), but objects within the original array remain unchanged.
class SpecificObject{
}
this.Objects = [{},{}];
this.Objects.map((signer,index) => {
//convert json generics to js objects
this.Objects[index] = new SpecificObject(signer);
});
console.log(this.Objects);
Not sure if the code snippet illustrates the problem correctly, but what i expect to see is:
[
SpecificObject{},
SpecificObject{}
]
I even tried cloning the original object to do the iteration but it didn't help. Must be missing something stupid. Is there a way to achieve what i need with lodash?
Edit(followed the answer advise):
Now i do the following:
console.log('signers-before', this.Signers);
this.Signers.map(signer => new SigningTableSigner(signer));
console.log('signers-after',this.Signers);
And this is what i get in my log(object still remain generic):
Edit2
Oops, i forgot the assignment. this.Signers =
now like this everything works as expected:
console.log('signers-before', this.Signers);
this.Signers = this.Signers.map(signer => new SigningTableSigner(signer));
console.log('signers-after',this.Signers);
There is no need to modify the collection while you are iterating it. Just return the object from the map.
this.Objects = this.Objects.map((signer,index) => new SpecificObject(signer));
Map is used when you wants to return an array. You can just use forEach for what you are trying to do. But I think this will look better
this.Objects = this.Objects.map(signer => new SpecificObject(signer))
You don't use map correctly. You should be just returning objects inside arrow functions. map actually returns new array, which contains elements which were created by applying function you have provided. Because of that you also have to reassign result ot this.Objects.
this.Objects = this.Objects.map(signer => {
//convert json generics to js objects
return new SpecificObject(signer);
})
I was looking at some snippets of code, and I found multiple elements calling a function over a node list with a forEach applied to an empty array.
For example I have something like:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
but I can't understand how it works. Can anyone explain me the behaviour of the empty array in front of the forEach and how the call works?
[] is an array.
This array isn't used at all.
It's being put on the page, because using an array gives you access to array prototypes, like .forEach.
This is just faster than typing Array.prototype.forEach.call(...);
Next, forEach is a function which takes a function as an input...
[1,2,3].forEach(function (num) { console.log(num); });
...and for each element in this (where this is array-like, in that it has a length and you can access its parts like this[1]) it will pass three things:
the element in the array
the index of the element (third element would pass 2)
a reference to the array
Lastly, .call is a prototype which functions have (it's a function which gets called on other functions).
.call will take its first argument and replace this inside of the regular function with whatever you passed call, as the first argument (undefined or null will use window in everyday JS, or will be whatever you passed, if in "strict-mode"). The rest of the arguments will be passed to the original function.
[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"
Therefore, you're creating a quick way to call the forEach function, and you're changing this from the empty array to a list of all <a> tags, and for each <a> in-order, you are calling the function provided.
EDIT
Logical Conclusion / Cleanup
Below, there's a link to an article suggesting that we scrap attempts at functional programming, and stick to manual, inline looping, every time, because this solution is hack-ish and unsightly.
I'd say that while .forEach is less helpful than its counterparts, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), it still serves purposes when all you really want to do is modify the outside world (not the array), n-times, while having access to either arr[i] or i.
So how do we deal with the disparity, as Motto is clearly a talented and knowledgeable guy, and I would like to imagine that I know what I'm doing/where I'm going (now and then... ...other times it's head-first learning)?
The answer is actually quite simple, and something Uncle Bob and Sir Crockford would both facepalm, due to the oversight:
clean it up.
function toArray (arrLike) { // or asArray(), or array(), or *whatever*
return [].slice.call(arrLike);
}
var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);
Now, if you're questioning whether you need to do this, yourself, the answer may well be no...
This exact thing is done by... ...every(?) library with higher-order features these days.
If you're using lodash or underscore or even jQuery, they're all going to have a way of taking a set of elements, and performing an action n-times.
If you aren't using such a thing, then by all means, write your own.
lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
var others = lib.array(arguments, 1);
return others.reduce(appendKeys, subject);
};
Update for ES6(ES2015) and Beyond
Not only is a slice( )/array( )/etc helper method going to make life easier for people who want to use lists just like they use arrays (as they should), but for the people who have the luxury of operating in ES6+ browsers of the relatively-near future, or of "transpiling" in Babel today, you have language features built in, which make this type of thing unnecessary.
function countArgs (...allArgs) {
return allArgs.length;
}
function logArgs (...allArgs) {
return allArgs.forEach(arg => console.log(arg));
}
function extend (subject, ...others) { /* return ... */ }
var nodeArray = [ ...nodeList1, ...nodeList2 ];
Super-clean, and very useful.
Look up the Rest and Spread operators; try them out at the BabelJS site; if your tech stack is in order, use them in production with Babel and a build step.
There's no good reason not to be able to use the transform from non-array into array... ...just don't make a mess of your code doing nothing but pasting that same ugly line, everywhere.
The querySelectorAll method returns a NodeList, which is similar to an array, but it's not quite an array. Therefore, it doesn't have a forEach method (which array objects inherit via Array.prototype).
Since a NodeList is similar to an array, array methods will actually work on it, so by using [].forEach.call you are invoking the Array.prototype.forEach method in the context of the NodeList, as if you had been able to simply do yourNodeList.forEach(/*...*/).
Note that the empty array literal is just a shortcut to the expanded version, which you will probably see quite often too:
Array.prototype.forEach.call(/*...*/);
The other answers have explained this code very well, so I'll just add a suggestion.
This is a good example of code that should be refactored for simplicity and clarity. Instead of using [].forEach.call() or Array.prototype.forEach.call() every time you do this, make a simple function out of it:
function forEach( list, callback ) {
Array.prototype.forEach.call( list, callback );
}
Now you can call this function instead of the more complicated and obscure code:
forEach( document.querySelectorAll('a'), function( el ) {
// whatever with the current node
});
It can be better written using
Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
});
What is does is document.querySelectorAll('a') returns an object similar to an array, but it does not inherit from the Array type.
So we calls the forEach method from the Array.prototype object with the context as the value returned by document.querySelectorAll('a')
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
It is basically the same as:
var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
// whatever with the current node
});
Want to update on this old question:
The reason to use [].foreach.call() to loop through elements in the modern browsers is mostly over. We can use document.querySelectorAll("a").foreach() directly.
NodeList objects are collections of nodes, usually returned by
properties such as Node.childNodes and methods such as
document.querySelectorAll().
Although NodeList is not an Array, it is possible to iterate over it
with forEach(). It can also be converted to a real Array using
Array.from().
However, some older browsers have not implemented NodeList.forEach()
nor Array.from(). This can be circumvented by using
Array.prototype.forEach() — see this document's Example.
Lots of good info on this page (see answer+answer+comment), but I recently had the same question as the OP, and it took some digging to get the whole picture. So, here's a short version:
The goal is to use Array methods on an array-like NodeList that doesn't have those methods itself.
An older pattern co-opted Array's methods via Function.call(), and used an array literal ([]) rather than than Array.prototype because it was shorter to type:
[].forEach.call(document.querySelectorAll('a'), a => {})
A newer pattern (post ECMAScript 2015) is to use Array.from():
Array.from(document.querySelectorAll('a')).forEach(a => {})
An empty array has a property forEach in its prototype which is a Function object. (The empty array is just an easy way to obtain a reference to the forEach function that all Array objects have.) Function objects, in turn, have a call property which is also a function. When you invoke a Function's call function, it runs the function with the given arguments. The first argument becomes this in the called function.
You can find documentation for the call function here. Documentation for forEach is here.
Just add one line:
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
And voila!
document.querySelectorAll('a').forEach(function(el) {
// whatever with the current node
});
Enjoy :—)
Warning: NodeList is a global class. Don't use this recomendation if you writing public library. However it's very convenient way for increasing self-efficacy when you work on website or node.js app.
Just a quick and dirty solution I always end up using. I wouldn't touch prototypes, just as good practice. Of course, there are a lot of ways to make this better, but you get the idea.
const forEach = (array, callback) => {
if (!array || !array.length || !callback) return
for (var i = 0; i < array.length; i++) {
callback(array[i], i);
}
}
forEach(document.querySelectorAll('.a-class'), (item, index) => {
console.log(`Item: ${item}, index: ${index}`);
});
[] always returns a new array, it is equivalent to new Array() but is guaranteed to return an array because Array could be overwritten by the user whereas [] can not. So this is a safe way to get the prototype of Array, then as described, call is used to execute the function on the arraylike nodelist (this).
Calls a function with a given this value and arguments provided
individually. mdn
Norguard explained WHAT [].forEach.call() does and James Allardice WHY we do it: because querySelectorAll returns a NodeList that doesn't have a forEach method...
Unless you have modern browser like Chrome 51+, Firefox 50+, Opera 38, Safari 10.
If not you can add a Polyfill:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
let's say you have : const myList= document.querySelectorAll("p");
This will return an list/array of all in your HTML.
Now Array.prototype.forEach.call(myList, myCallback)
is equivalent to [].forEach.call(myList, myCallback)
where 'myCallback' is a callback function.
You are basically running the callback function on each element of myList.
Hope this helped you!
I don't know if there is any restriction, but it works.
I turned the nodeList into an iterator object using the spread operator and mapped it:
let _btns = document.querySelectorAll('.btn');
[..._btns].map(function(elem, i) {
elem.addEventListener('click', function (e) {
console.log(elem.textContent);
})
})
.btn {
padding: 5px;
color:#fff;
background-color: darkred;
text-align:center;
color: white;
}
<button class="btn">button 1</button>
<button class="btn">button 2</button>
My Task
In my JavaScript code i'm often using objects to "map" keys to values so i can later access them directly through a certain value. For example:
var helloMap = {};
helloMap.de = "Hallo";
helloMap["en"] = "Hello";
helloMap.es = "Hola";
So i build up the map object step by step in my source code using the two available notations object style and array style.
Later i can then access the values i added through helloMap["de"] for example. So thats all fine if i don't have to care about the order in which the attributes has been set on the object.
If i want to iterate the objects properties now as far as i know there is no way to ensure that i'll iterate them in the order they have been added (insertion order).
Note: I can't use some wrapper object and simply hold a array in there and then use its methods to add the values so something like this:
var HelloMap = function(){
this.myMap = [];
this.addProperty = function(key, value){
this.myMap.push({key: key, value: value});
}
}
or something similar won't work for me. So the solution needs to be absolutely transparent to the programmer using the object.
That said the object i needed would be an empty object which maintains the order of the properties that were added to it. Something like this would do:
var helloMap = {};
helloMap = getOrderAwareObject(helloMap);
so that every further assignment of the form helloMap.xy = "foo" and helloMap["yz"] = "bar" would be tracked in the object "in order",
Possible Solutions
Since i did not find any solution in underscore or jQuery giving me such a special object i came across the possibility of defining getters and setters for properties in JavaScript objects with Object.defineProperty since i can rely on ECMAScript 5 standard i can use it.
The Problem with this one is, that you have to know all the possible properties that can be set on the object, before they are actually set. Since if you define it you got to name it.
What i am searching for is something like a Default Getter and Default Setter which applies on the object if no getter and setter has been defined for the property. So i could then hide the sorted map behind the object inteface.
Is there already a solution for this in any framework you know?
Is there a mechanism like "default getter/setter" ?
You'll need a wrapper of some kind using an array internally, I'm afraid. ECMAScript 5 (which is the standard on which current browser JavaScript implementations are based) simply doesn't allow for ordered object properties.
However, ECMAScript 6 will have a Map implementation that has ordered properties. See also http://www.nczonline.net/blog/2012/10/09/ecmascript-6-collections-part-2-maps/.
There may also be other options in ECMAScript 6. See the following question:
How can I define a default getter and setter using ECMAScript 5?
Adding a link to a custom javascript library which provides Sorted maps and other implementation, for future reference in this thread . Check out https://github.com/monmohan/dsjslib
-msingh
I don't know of a general solution but non-general solutions are very simple to construct.
Typically, you maintain an Array of objects, with several methods defined as properties of the Array. At least, that's my approach.
Here's an example, taken (in a modified form) from a larger application :
var srcs = [];
srcs.find = function(dist) {
var i;
for(i=0; i<this.length; i++) {
if(dist <= this[i].dist) { return this[i]; }
}
return null;
};
srcs.add = function(dist, src) {
this.push({ dist:dist, src:src });
}
srcs.remove = function(dist) {
var i;
for(i=0; i<this.length; i++) {
if(this[i].dist === dist) {
srcs.splice(i,1);
return true;
}
}
return false;
};
srcs.add(-1, 'item_0.gif' );
srcs.add(1.7, 'item_1.gif');
srcs.add(5, 'item_2.gif');
srcs.add(15, 'item_3.gif');
srcs.add(90, 'item_4.gif');
Unfortunately, you lose the simplicity of a plain js object lookup, but that's the price you pay for having an ordered entity.
If you absolutely must have order and dot.notation, then maintain a plain js Object for lookup and an Array for order. With care, the two can be maintained with total integrity.
See my answer to this question. I implemented an basic ordered hashtable (ES 5+ only, didn't bother to polyfill)
var put = function(k,v){
if(map[k]){
console.log("Key "+ k+" is already present");
}else
{
var newMap = {};
map[k] = v;
Object.keys(map).sort().forEach(function(key){
newMap[key] = map[key];
});
map = newMap;
//delete newMap; in case object memory need to release
return map;
}
}
Put method will always take a key-value pair, internally creates another map with sorted keys from the actual map, update the value and return the updated map with sorted keys.No external library need to includ.
I have a string that has data separated by a pipe character (|).
Example
var somestring = "data1|data2|data3";
var separated = somestring.split("|");
I know how to use the split() to separate each data.
However, I don't know how many pipes there will be in the resulting Array.
In jQuery or JavaScript, how do I loop over the array returned?
In jQuery or JavaScript, how do I loop through each separated variable?
You basically just need to iterate over the resulting Array.
jQuery
$.each loop
This method is easy to work with, and benefits in the variables used being encapsulated.
$.each(separated, function(index, chunk) {
// `chunk` is each member of the array.
});
jsFiddle.
Of course, jQuery is JavaScript so any of the below methods will also work.
JavaScript
for loop
This is the recommended way.
for (var i = 0, length = separated.length; i < length; i++) {
var chunk = separated[i];
// `chunk` is each member of the array.
}
jsFiddle.
You'll notice too the length property is cached so it is not looked up on each iteration. Some browsers already optimise for this, however IE appears to still benefit from it cached. It only takes 5 seconds to do, so you may as well keep IE users happy too.
You may want to define i and chunk outside of the for loop, because JavaScript has no block scope (unless you're using let), and those variables will exist before (declaration hoisted) and after (no block scope).
for ( in ) loop
This loop is generally not recommended, as it should be used for iterating over object properties only, not array like member properties.
for (var chunk in separated) {
if ( ! separated.hasOwnProperty(chunk)) {
continue;
}
// `separated[chunk]` is each member of the array.
}
jsFiddle.
This loop will loop over all properties up the prototype chain, so hasOwnProperty() must be used. For this reason it is not recommended for arrays.
for ( of ) loop
This loop is standardized in ECMA 6 and is able to loop over NodeLists and iterators.
for (var chunk of separated) {
// `chunk` is each member of the array.
}
jsFiddle
forEach() method
This method is an addition to the ECMA-262 standard. It's not available in IE8, but it can be shimmed relatively easily.
separated.forEach(function(chunk, index) {
// `chunk` is each member of the array.
});
jsFiddle.
Other specialised methods
If you're looking to iterate for a specific goal, it may be useful to use a specialised iterator. Keep in mind these also don't have the best browser support.
filter method
Creates a mew array of the elements which the associated callback returned truthy for.
separated.filter(function(element) {
return +element > 255;
});
reduce method
Creates a new value based on reducing the elements of the array, from left to right.
separated.reduce(function(accumulator, element) {
return accumulator.concat(element);
}, "");
See also the reduceRight method.
map method
Creates a new array, replacing each element with the returned value of the associated callback.
separated.map(function(element) {
return element.substr(0, 1);
});
every method
Returns a boolean value of which is the result of every element in the array passing the test. This method short circuits, i.e. it returns whenever one element's callback doesn't return truthy.
separated.every(function(element) {
return element.substr(0, 1) == "a";
});
some method
Returns a boolean value of which is the result of some element in the array passing the test. This method short circuits, i.e. it returns whenever one element's callback passes the test.
separated.some(function(element) {
return element.substr(0, 1) == "a";
});
separated.length should be all you need.
str.split() returns an array of values, so in your example, since 'separated' is an array, you could:
for (var i=0, len=separated.length; i < len; i++) {
// do something with separated[i]
}
you can do it in jquery like this
$.each(separated,function(key,item){ alert('here is ' + item + ' at position ' + key) })
If your question really is "how do I loop through each separated variable?" then:
for (var i = 0; i < separated.length; i++)
{
//Do something with separated[i];
}
//or (apparently this is deprecated)
for(var a in separated)
{
//Do something with a
}
Loop through with a FOR...NEXT construct like in most other languages:
var somestring = "data1|data2|data3";
var separated = somestring.split("|");
for (i=0 ; i<separated.length; i++) {
document.write(separated[i]);
document.write("<br/>");
}