While I was trying to check how many times an element used in an array, I found this code. It is written by another user and I got it to work but I am trying to figure out why he used "{}" at the end. I know that .reduce() method can get initialValue but I could not understand the use of braces.
var a = ["a","b","b","c","a","b","d"];
var map = a.reduce(function(obj, b) { obj[b] = ++obj[b] || 1;
return obj;
}, {});
I thought that they might be the initialValue parameter since it covers the result, but when I tried to remove the braces the result was not the same. I also checked the MDN documents, found some similar code but could not wrap my mind around it since I am quite new in JavaScript.
When we use the braces I get :
{
a: 2,
b: 3,
c: 1,
d: 1
}
But when I remove the braces and run it I get:
a
I tried using brackets and it resulted as : [ a: 2, b: 3, c: 1, d: 1 ],
So it seems the braces enclose the values but shouldn't it work as usual without braces?
But when I remove the braces and run it, I get: a
This is the syntax:
arr.reduce(callback[, initialValue])
initialValue : Value to use as the first argument to the first call of the callback. If no initial value is supplied, the first element in the array will be used."
So, if you use reduce without the initialValue({}), the first item in the array will be used as the initialValue which is "a"
So, it becomes similar to:
var a = ["a", "b", "b", "c", "a", "b", "d"];
var map = a.slice(1).reduce(function(obj, b) {
obj[b] = ++obj[b] || 1;
return obj;
}, "a");
console.log(map)
In the first iteration,
obj[b] = ++obj[b] || 1;
becomes
"a"["b"] = ++"a"["b"] || 1
This neither throws an exception, nor does it change the obj string. obj is still "a" and it will be returned every time.
The braces {} represent a new empty object in javascript, In your case, it will be the object returned by the reduce method to the map variable, we need to initialize it first then fill it in the core of the reduce callback.
Value to use as the first argument to the first call of the callback. If no initial value is supplied, the first element in the array will be used. Calling reduce() on an empty array without an initial value is an error.
It's the initialValue take a look to reduce(), here's a sample, If you were to provide an initial value as the second argument to reduce(), the result would look like this:
let arr = [0, 1, 2, 3, 4];
arr = arr.reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator + currentValue;
}, 10);
console.log(arr);
That is the accumulator object.You can say that it is the initial value so when the call back function will be executed the initial value will be a empty object.
So in the example below initially it is passing an object which have key e
var a = ["a", "b", "b", "c", "a", "b", "d"];
var map = a.reduce(function(obj, b) {
console.log(obj)
obj[b] = ++obj[b] || 1;
return obj;
}, {e:'test'});
console.log(map)
The second argument in the .reduce() method is the initialValue, which is a
Value to use as the first argument to the first call of the callback. If no initial value is supplied, the first element in the array will be used.
tl;dr
It's the initial value which .reduce() starts with. This is the first argument passed to the callback in the first call.
In your case the idea was to build a map of values from the array where keys in the map were the values from the array and values in the map were a number of occurrences of that value in the array.
A map in JS can be easily simulated by an object which in your case has been passed as a literal {} to the .reduce() method. The method fires the callback for each element in the array passing the result object as the first argument and the current element in the array as the second. But the problem is at the first call - what value should be used as the result object if there were no previous elements in the array to accumulate? That's why you need to pass some initial value to have something to start with. As the MDN states, if no initialValue is passed, the first element of the array is used - that's why you got a when removed initial value. When you passed [] you told JS to have an array literal as the initial value but in the callback, you treat it as an object which is allowed in JS since an array is also an object. The problem arises when you try to iterate over those properties or stringify them using JSON.stringify(). But it's for another story ;)
{} create new object if you don't add this then the first element in the array will be used.
You can see that when you run the code with {} you get an empty object as the initialValue and fulfills your requirement.
var a = ["a","b","b","c","a","b","d"];
var map = a.reduce(function(obj, b) {
"use strict";
if (Object.entries(obj).length === 0 && obj.constructor === Object) {
console.log("InitialValue is defined as object: ", obj);
}
obj[b] = ++obj[b] || 1;
return obj;
}, {});
console.log(map);
Whereas without {} it assigns the first value of array a to the obj that means now obj is a string and when you try to use it as an object then it throws error as in the below code.
var a = ["a","b","b","c","a","b","d"];
var map = a.reduce(function(obj, b) {
"use strict";
console.log("InitialValue not defined: ", obj);
obj[b] = ++obj[b] || 1;
return obj;
});
console.log(map);
I have just added "use strict" to show this error.
Related
I would like to filter an array of items by using the map() function. Here is a code snippet:
var filteredItems = items.map(function(item)
{
if( ...some condition... )
{
return item;
}
});
The problem is that filtered out items still uses space in the array and I would like to completely wipe them out.
Any idea?
EDIT: Thanks, I forgot about filter(), what I wanted is actually a filter() then a map().
EDIT2: Thanks for pointing that map() and filter() are not implemented in all browsers, although my specific code was not intended to run in a browser.
You should use the filter method rather than map unless you want to mutate the items in the array, in addition to filtering.
eg.
var filteredItems = items.filter(function(item)
{
return ...some condition...;
});
[Edit: Of course you could always do sourceArray.filter(...).map(...) to both filter and mutate]
Inspired by writing this answer, I ended up later expanding and writing a blog post going over this in careful detail. I recommend checking that out if you want to develop a deeper understanding of how to think about this problem--I try to explain it piece by piece, and also give a JSperf comparison at the end, going over speed considerations.
That said, **The tl;dr is this:
To accomplish what you're asking for (filtering and mapping within one function call), you would use Array.reduce()**.
However, the more readable and (less importantly) usually significantly faster2 approach is to just use filter and map chained together:
[1,2,3].filter(num => num > 2).map(num => num * 2)
What follows is a description of how Array.reduce() works, and how it can be used to accomplish filter and map in one iteration. Again, if this is too condensed, I highly recommend seeing the blog post linked above, which is a much more friendly intro with clear examples and progression.
You give reduce an argument that is a (usually anonymous) function.
That anonymous function takes two parameters--one (like the anonymous functions passed in to map/filter/forEach) is the iteratee to be operated on. There is another argument for the anonymous function passed to reduce, however, that those functions do not accept, and that is the value that will be passed along between function calls, often referred to as the memo.
Note that while Array.filter() takes only one argument (a function), Array.reduce() also takes an important (though optional) second argument: an initial value for 'memo' that will be passed into that anonymous function as its first argument, and subsequently can be mutated and passed along between function calls. (If it is not supplied, then 'memo' in the first anonymous function call will by default be the first iteratee, and the 'iteratee' argument will actually be the second value in the array)
In our case, we'll pass in an empty array to start, and then choose whether to inject our iteratee into our array or not based on our function--this is the filtering process.
Finally, we'll return our 'array in progress' on each anonymous function call, and reduce will take that return value and pass it as an argument (called memo) to its next function call.
This allows filter and map to happen in one iteration, cutting down our number of required iterations in half--just doing twice as much work each iteration, though, so nothing is really saved other than function calls, which are not so expensive in javascript.
For a more complete explanation, refer to MDN docs (or to my post referenced at the beginning of this answer).
Basic example of a Reduce call:
let array = [1,2,3];
const initialMemo = [];
array = array.reduce((memo, iteratee) => {
// if condition is our filter
if (iteratee > 1) {
// what happens inside the filter is the map
memo.push(iteratee * 2);
}
// this return value will be passed in as the 'memo' argument
// to the next call of this function, and this function will have
// every element passed into it at some point.
return memo;
}, initialMemo)
console.log(array) // [4,6], equivalent to [(2 * 2), (3 * 2)]
more succinct version:
[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])
Notice that the first iteratee was not greater than one, and so was filtered. Also note the initialMemo, named just to make its existence clear and draw attention to it. Once again, it is passed in as 'memo' to the first anonymous function call, and then the returned value of the anonymous function is passed in as the 'memo' argument to the next function.
Another example of the classic use case for memo would be returning the smallest or largest number in an array. Example:
[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
// ^this would return the largest number in the list.
An example of how to write your own reduce function (this often helps understanding functions like these, I find):
test_arr = [];
// we accept an anonymous function, and an optional 'initial memo' value.
test_arr.my_reducer = function(reduceFunc, initialMemo) {
// if we did not pass in a second argument, then our first memo value
// will be whatever is in index zero. (Otherwise, it will
// be that second argument.)
const initialMemoIsIndexZero = arguments.length < 2;
// here we use that logic to set the memo value accordingly.
let memo = initialMemoIsIndexZero ? this[0] : initialMemo;
// here we use that same boolean to decide whether the first
// value we pass in as iteratee is either the first or second
// element
const initialIteratee = initialMemoIsIndexZero ? 1 : 0;
for (var i = initialIteratee; i < this.length; i++) {
// memo is either the argument passed in above, or the
// first item in the list. initialIteratee is either the
// first item in the list, or the second item in the list.
memo = reduceFunc(memo, this[i]);
// or, more technically complete, give access to base array
// and index to the reducer as well:
// memo = reduceFunc(memo, this[i], i, this);
}
// after we've compressed the array into a single value,
// we return it.
return memo;
}
The real implementation allows access to things like the index, for example, but I hope this helps you get an uncomplicated feel for the gist of it.
That's not what map does. You really want Array.filter. Or if you really want to remove the elements from the original list, you're going to need to do it imperatively with a for loop.
Array Filter method
var arr = [1, 2, 3]
// ES5 syntax
arr = arr.filter(function(item){ return item != 3 })
// ES2015 syntax
arr = arr.filter(item => item != 3)
console.log( arr )
You must note however that the Array.filter is not supported in all browser so, you must to prototyped:
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.filter)
{
Array.prototype.filter = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
{
var val = this[i]; // in case fun mutates this
if (fun.call(thisp, val, i, this))
res.push(val);
}
}
return res;
};
}
And doing so, you can prototype any method you may need.
TLDR: Use map (returning undefined when needed) and then filter.
First, I believe that a map + filter function is useful since you don't want to repeat a computation in both. Swift originally called this function flatMap but then renamed it to compactMap.
For example, if we don't have a compactMap function, we might end up with computation defined twice:
let array = [1, 2, 3, 4, 5, 6, 7, 8];
let mapped = array
.filter(x => {
let computation = x / 2 + 1;
let isIncluded = computation % 2 === 0;
return isIncluded;
})
.map(x => {
let computation = x / 2 + 1;
return `${x} is included because ${computation} is even`
})
// Output: [2 is included because 2 is even, 6 is included because 4 is even]
Thus compactMap would be useful to reduce duplicate code.
A really simple way to do something similar to compactMap is to:
Map on real values or undefined.
Filter out all the undefined values.
This of course relies on you never needing to return undefined values as part of your original map function.
Example:
let array = [1, 2, 3, 4, 5, 6, 7, 8];
let mapped = array
.map(x => {
let computation = x / 2 + 1;
let isIncluded = computation % 2 === 0;
if (isIncluded) {
return `${x} is included because ${computation} is even`
} else {
return undefined
}
})
.filter(x => typeof x !== "undefined")
I just wrote array intersection that correctly handles also duplicates
https://gist.github.com/gkucmierz/8ee04544fa842411f7553ef66ac2fcf0
// array intersection that correctly handles also duplicates
const intersection = (a1, a2) => {
const cnt = new Map();
a2.map(el => cnt[el] = el in cnt ? cnt[el] + 1 : 1);
return a1.filter(el => el in cnt && 0 < cnt[el]--);
};
const l = console.log;
l(intersection('1234'.split``, '3456'.split``)); // [ '3', '4' ]
l(intersection('12344'.split``, '3456'.split``)); // [ '3', '4' ]
l(intersection('1234'.split``, '33456'.split``)); // [ '3', '4' ]
l(intersection('12334'.split``, '33456'.split``)); // [ '3', '3', '4' ]
First you can use map and with chaining you can use filter
state.map(item => {
if(item.id === action.item.id){
return {
id : action.item.id,
name : item.name,
price: item.price,
quantity : item.quantity-1
}
}else{
return item;
}
}).filter(item => {
if(item.quantity <= 0){
return false;
}else{
return true;
}
});
following statement cleans object using map function.
var arraytoclean = [{v:65, toberemoved:"gronf"}, {v:12, toberemoved:null}, {v:4}];
arraytoclean.map((x,i)=>x.toberemoved=undefined);
console.dir(arraytoclean);
I'm trying to discover if an object has some properties and I'm having trouble using the hasOwnProperty method.
I'm using the method on an array (I know the documentation states a string).
The following line returns true:
{ "a": 1, "b": 2 }.hasOwnProperty( ["a"]);
This line returns also true:
{ "a": 1, "b": 2 }.hasOwnProperty( "a", "b");
But this one returns false:
{ "a": 1, "b": 2 }.hasOwnProperty( ["a", "b"])
And I need it to return true. I'm using Object.keys(object) to get the properties that I'm using, and it returns me an array, so I need to use an array on hasOWnProperty.
Is there some theoric concept I'm missing? And is there some way to fix this problems?
There are two things going on here.
First, hasOwnProperty only takes one argument. So it'll ignore whatever other arguments you pass to it.
Second, (and I'm simplifying slightly here) it's going to convert that first argument to a String, and then check to see if the object has that property.
So let's look at your test cases:
The reason { "a": 1, "b": 2 }.hasOwnProperty( "a", "b"); returns true is because it's ignoring the second argument. So really it's just checking for "a".
{ "a": 1, "b": 2 }.hasOwnProperty( ["a", "b"]) returns false because the first argument, ["a", "b"], gets converted to "a,b", and there's no { "a": 1, "b": 2 }["a,b"].
To find out if a given object has all the properties in an array, you can loop over the array and check each property, like so:
function hasAllProperties(obj, props) {
for (var i = 0; i < props.length; i++) {
if (!obj.hasOwnProperty(props[i]))
return false;
}
return true;
}
Or, if you're feeling fancy, you can use the every function to do this implicitly:
var props = ["a", "b"];
var obj = { "a": 1, "b": 2 };
var hasAll = props.every(prop => obj.hasOwnProperty(prop));
I hope that helps clarify things. Good luck!
If you want to check the object's own properties, you could use the Object.getOwnPropertyNames method. It returns an array of all properties (including non-enumerable properties except for those which use Symbol) found directly upon a given object.
let o = { "a": 1, "b": 2 };
Object.getOwnPropertyNames(o).forEach(k => console.log(`key: ${k}, value: ${o[k]}`));
Given the documentation, it seems that the hasOwnProperty() method takes either a string or a symbol as an argument. So I think hasOwnProperty() isn't capable of taking a collection of strings and testing if the object has each one as a property.
I think another approach would be to take the array of strings and iterate through each one. Then for each string in your array (the properties you want to test for), you could test if the object has that property. Here's an example:
const o = new Object();
var propsToTest = ['a', 'b'];
o.a = 42;
o.b = 40;
var hasProperties = true;
propsToTest.forEach(function(element) { // For each "property" in propsToTest, verify that o hasOwnProperty
if(!o.hasOwnProperty(element))
hasProperties = false;
});
console.log(hasProperties);
First, in terms of getting your third snippet to return true, I don't think that's possible. The best you could do would be to check each property individually:
const obj = { "a": 1, "b": 2 };
console.log(["a", "b"].every(p => obj.hasOwnProperty(p)))
Which should give you what you want.
But to understand why the first two returned true, but the third false:
The hasOwnProperty method only accepts a single argument (additional arguments are ignored), and it expects that argument to be a string. If the argument is not a string, then JavaScript will attempt to coerce it to one (usually by using a .toString method).
So your first two snippets are equivalent because:
["a"].toString() === "a", so hasOwnProperty(["a"]) is the same as hasOwnProperty("a") after the argument is converted to a string.
Then in your second snippet, the second argument "b" is simply ignored, leaving it equivalent to just hasOwnProperty("a") again.
And finally, your third snippet uses ["a", "b"], and ["a", "b"].toString() === "a,b", which is not a property of your object.
So this is an old question and I'm kinda surprised no one else has thought to write it but this is usually solved using .every on the array. So for the original question, the code should be something like:
["a", "b"].every((item) => ({ "a": 1, "b": 2 }.hasOwnProperty(item)))
This will return a simple true.
Array .every will run a condition for every item in an array and only return true if the condition is true for all item.
See the the mozilla web docs
You can achieve this with a for...in loop like this
const obj = { "a": 1, "b": 2 };
for (key in obj) {
if (obj.hasOwnProperty(key)) {
console.log('has', key, obj[key]);
} else {
console.log('not', key, obj[key]);
}
}
I have a question here. I know my code have plenty of problems and I need your help.
The problem is, To return an object which have numbers of repetition information of the string.
The given string is
var r = countFreq(["a", "b", "b", "c", "c", "c", "d"]);
and the result have to be
{"a":1, "b":2, "c":3, "d":1 }
by console.log(r);
All that I know is, key(properties) have to be element and value(value of property) have to be the number of repetition.
AND, I must use 'in' key world to solve this problem.
Like,
if('property' in 'object') {
//...
}else {
//...
}
(if there's no property initialize as 1, and if there's a same property, add 1 each time)
I really appreciate your help.
(This post may have grammatical errors. I really feel sorry about that...)
function countFreq(array) {
var i;
for(i=0; i<array.length; i++)
{
if(array[i] in array)
{
return i += 1;
}else
{
return i = 1;
}
console.log(array[i]+": "+i+", ");
}
}
var r = countFreq(["a","b","c","c","c","d"]);
console.log(r);
According to MDN - The 'in' operator returns true if the specified property is in the specified object or its prototype chain.
Prop is a string or symbol representing a property name or array index (non-symbols will be coerced to strings).
Object is to check if it (or its prototype chain) contains the property with specified name.
So in your case, it depends what your object is? if you object is an array, you need to use prop as properties of array. All index values up to length of array will return true.
MDN Example of arrays
var trees = ['redwood', 'bay', 'cedar', 'oak', 'maple'];
0 in trees // returns true
3 in trees // returns true
6 in trees // returns false
'bay' in trees // returns false (you must specify the
// index number, not the value at that index)
'length' in trees // returns true (length is an Array property)
Symbol.iterator in trees // returns true (arrays are iterable, works only in ES2015+)
I think you are misunderstanding the in operator. In can be used in 2 cases, as a boolean operator to check for the presence of an index in an array or to iterate over the indexes of an array with a for loop. You are using it to check for the presence of a value in an array directly, which you cannot do. Also you are returning from the function after each iteration so you will only ever get 1 or 0.
I presume you want something like the following:
countFreq(array) {
var results = { a: 0, b: 0, c: 0, d: 0 };
for (var index in array) {
results[array[index]] ++;
}
return results;
}
Now you can access each result with results[‘a’] for instance, after calling countFreq. I think you need to read up on return and loops in JavaScript.
I've answered a question here before about How to get number of response of JSON? and I suggested for them to use the map function instead of using a for loop but someone commented that .map is not for looping and to use forEach instead.
Are there any downsides to using map over a for loop?
I also researched this and found a site stating that map > forEach .
Map is used to transform each element in an array into another representation, and returns the results in a new sequence. However, since the function is invoked for each item, it is possible that you could make arbitrary calls and return nothing, thus making it act like forEach, although strictly speaking they are not the same.
Proper use of map (transforming an array of values to another representation):
var source = ["hello", "world"];
var result = source.map(function(value) {
return value.toUpperCase();
});
console.log(result); // should emit ["HELLO, "WORLD"]
Accidentally using .map to iterate (a semantic error):
var source = ["hello", "world"];
// emits:
// "hello"
// "world"
source.map(function(value) {
console.log(value);
});
The second example is technically valid, it'll compile and it'll run, but that is not the intended use of map.
"Who cares, if it does what I want?" might be your next question. First of all, map ignores items at an index that have an assigned value. Also, because map has an expectation to return a result, it is doing extra things, thus allocating more memory and processing time (although very minute), to a simple process. More importantly, it may confuse yourself or other developers maintaining your code.
The map() method creates a new array with the results of calling a
provided function on every element in this array.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.
If a thisArg parameter is provided to map, it will be passed to callback when invoked, for use as its this value. Otherwise, the value undefined will be passed for use as its this value. The this value ultimately observable by callback is determined according to the usual rules for determining the this seen by a function.
map does not mutate the array on which it is called (although callback, if invoked, may do so).
The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback. If existing elements of the array are changed, or deleted, their value as passed to callback will be the value at the time map visits them; elements that are deleted are not visited.
Ref from MDN:
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the |this|
// value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal
// method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let A be a new array created as if by the expression new Array(len)
// where Array is the standard built-in constructor with that name and
// len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal
// method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Let mappedValue be the result of calling the Call internal
// method of callback with T as the this value and argument
// list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor
// { Value: mappedValue,
// Writable: true,
// Enumerable: true,
// Configurable: true },
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, k, {
// value: mappedValue,
// writable: true,
// enumerable: true,
// configurable: true
// });
// For best browser support, use the following:
A[k] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
The best way to think about map is to think of it as a "functional" for loop with some superpowers.
When you call .map on an array, two things happen.
You give it a function, and that function gets called every time it iterates. It passes the item into your function at the current index of the loop. Whatever value you return in this function "updates" that given item in a new array.
The .map function returns you an array of all the values that got returned by the function you give to map.
Let's look at an example.
var collection = [1, 2, 3, 4];
var collectionTimesTwo = collection.map(function (item) {
return item * 2;
});
console.log(collection) // 1, 2, 3, 4
console.log(collectionPlusOne) // 2, 4, 6, 8
On the first line we define our original collection, 1 through 4.
On the next couple line we do our map. This is going to loop over every item in the collection, and pass each item to the function. The function returns the item multiplied by 2. This ends up generating a new array, collectionTimesTwo -- the result of multiplying each item in the array by two.
Let's look at one more example, say we have a collection of words and we want to capitalize each one with map
var words = ['hello', 'world', 'foo', 'bar'];
var capitalizedWords = words.map(function (word) {
return word.toUpperCase();
})
console.log(words) // 'hello', 'world', 'foo', 'bar'
console.log(capitalizedWords) // 'HELLO', 'WORLD', 'FOO', 'BAR'
See where we're going?
This lets us work more functionally, rather than like the following
var words = ['hello', 'world', 'foo', 'bar'];
var capitalizedWords = [];
for (var i = 0; i < words.length; i++) {
capitalizedWords[i] = words[i].toUpperCase();
}
There are a few things that can be said objectively, disregarding the subjective parts.
What is clear and concise use? If I use map() anyone reading the code assumes I'm doing what it says: mapping the values somehow. Being it a lookup table, calculation or whatever. I take the values and return (the same amount of) values transformed.
When I do forEach() it is understood I will use all the values as input to do something but I'm not doing any transformations and not returning anything.
Chaining is just a side effect, not a reason to use one over the other. How often do your loops return something you can or want to reuse in a loop, unless you're mapping?
Performance. Yes, it might be micro-optimization, but why use a function that causes an array to be gathered and returned if you're not going to use it?
The blog post you linked to is quite messy. It talks about for using more memory and then recommends map() because it's cool, even though it uses more memory and is worse in performance.
Also as an anecdote the test linked to there runs for faster than forEach on my one browser. So objective performance cannot be stated.
Even if opinions shouldn't count on SO, I believe this to be the general opinion: use methods and functions that were made for that use. Meaning for or forEach() for looping and map() for mapping.
The phrase "map isn't for looping" was probably a little bit inaccurate, since of course map replaces for-loops.
What the commenter was saying was that you should use forEach when you just want to loop, and use map when you want to collect results by applying an operating to each of the array elements. Here is a simple example:
> a = [10, 20, 30, 40, 50]
[ 10, 20, 30, 40, 50 ]
> a.map(x => x * 2)
[ 20, 40, 60, 80, 100 ]
> count = 0;
0
> a.forEach(x => count++)
undefined
> count
5
Here map retains the result of applying a function to each element. You map when you care about each of the individual results of the operation. In contrast, in your case of counting the number of elements in an array, we don't need to produce a new array. We care only about a single result!
So if you just want to loop, use forEach. When you need to collect all of your results, use map.
The list processing routine map on an array object is very convenient at times. Here's one of the handy ways to use it:
var numarr = [1,2,3,4];
console.log(numarr.map(String))
>>> ["1", "2", "3", "4"]
I took this for granted thus far. Today I was however puzzled by it. What the map function is returning above is an array of strings. We typically pass a function to map as argument. In above case we pass String object. String is implemented inside the Javascript implementation, so I don't know what kind of specialities it has. The above code works as if a new instance of String is created for each item in array.
If it's not clear, consider this. If I decide to implement an object in Javascript say MyString and pass it to map, I won't get the above behavior.
function MyString(x) { this.val = x; }
MyString.prototype.toString = function () { return String(this.val); };
var ms = new MyString(4)
console.log(String(ms));
>>> "4"
var arr = [1,2,3];
arr.map(MyString)
>>> [undefined, undefined, undefined]
Does anyone know why then arr.map(String) works the way it does?
Update: A comment I added below clarifies my question better.
At the end of the 2nd snippet, try console.log(val). You'll notice you've leaked a global:
var arr = [1,2,3];
arr.map(MyString);
console.log(val); // "3"
When using arr.map(MyString), you're calling that constructor as a function, without the new to create instances. And, since MyString doesn't return anything, you get undefined in the results. But, you've still set this.val, while this isn't an instance but is rather the global object.
String doesn't return undefined because it has a return when called without new:
When String is called as a function rather than as a constructor, it performs a type conversion.
Returns a String value (not a String object) computed by ToString(value). If value is not supplied, the empty String "" is returned.
You can imitate this with MyString by checking if this is an instance first, returning a new instance when this isn't one already:
function MyString(x) {
if (this instanceof MyString) {
this.val = x;
} else {
return new MyString(x);
}
}
var arr = [1, 2, 3];
arr.map(MyString); // [ {val: "1"}, {val: "2"}, {val: "3"} ]
Array.map returns an array whose elements are the value returned by applying the specified function to each value in the this array. String is a function; it returns a string. That's all there is to it.
Thats is because String is a function. It returns a string constructed from what is passed to it. For example, if you call String(100), it will return "100".