Problem with array assignment in javascript [duplicate] - javascript

Ever since its introduction in ECMA-262, 3rd Edition, the Array.prototype.push method's return value is a Number:
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
What were the design decisions behind returning the array's new length, as opposed to returning something potentially more useful, like:
A reference to the newly appended item/s
The mutated array itself
Why was it done like this, and is there a historical record of how these decisions came to be made?

I understand the expectation for array.push() to return the mutated array instead of its new length. And the desire to use this syntax for chaining reasons.
However, there is a built in way to do this: array.concat().
Note that concat expects to be given an array, not an item. So, remember to wrap the item(s) you want to add in [], if they are not already in an array.
newArray = oldArray.concat([newItem]);
Array chaining can be accomplished by using .concat(), as it returns an array,
but not by .push(), as it returns an integer (the new length of the array).
Here is a common pattern used in React for changing the state variable, based on its prior value:
// the property value we are changing
selectedBook.shelf = newShelf;
this.setState((prevState) => (
{books: prevState.books
.filter((book) => (book.id !== selectedBook.id))
.concat(selectedBook)
}
));
state object has a books property, that holds an array of book.
book is an object with id, and shelf properties (among others).
setState() takes in an object that holds the new value to be assigned to state
selectedBook is already in the books array, but its property shelf needs to be changed.
We can only give setState a top level object, however.
We cannot tell it to go find the book, and look for a property on that book, and give it this new value.
So we take the books array as it were.
filter to remove the old copy of selectedBook.
Then concat to add selectedBook back in, after updating its shelf property.
Great use case for wanting to chain push.
However, the correct way to do this is actually with concat.
Summary:
array.push() returns a number (mutated array's new length).
array.concat([]) returns a new array.
Technically, it returns a new array with the modified element added to the end, and leaves the initial arrays unchanged.
Returning a new array instance, as opposed to recycling the existing array instance is an important distinction, that makes it very useful for state objects in React applications, to get changed data to re-render.

I posted this in TC39's communication hub, and was able to learn a bit more about the history behind this:
push, pop, shift, unshift were originally added to JS1.2 (Netscape 4) in 1997.
There were modeled after the similarly named functions in Perl.
JS1.2 push followed the Perl 4 convention of returning the last item pushed.
In JS1.3 (Netscape 4.06 summer 1998) changed push to follow the Perl 5 conventions of returning the new length of the array.
see original jsarray.c source
/*
* If JS1.2, follow Perl4 by returning the last thing pushed. Otherwise,
* return the new array length.
*/

I cannot explain why they chose to return the new length, but in response to your suggestions:
Returning the newly appended item:
Given that JavaScript uses C-style assignment which emits the assigned value (as opposed to Basic-style assignment which does not) you can still have that behavior:
var addedItem;
myArray.push( addedItem = someExpression() );
(though I recognise this does mean you can't have it as part of an r-value in a declaration+assignment combination)
Returning the mutated array itself:
That would be in the style of "fluent" APIs which gained popularity significantly after ECMAScript 3 was completed and it would not be keeping in the style of other library features in ECMAScript, and again, it isn't that much extra legwork to enable the scenarios you're after by creating your own push method:
Array.prototype.push2 = function(x) {
this.push(x);
return this;
};
myArray.push2( foo ).push2( bar ).push2( baz );
or:
Array.prototype.push3 = function(x) {
this.push(x);
return x;
};
var foo = myArray.push3( computeFoo() );

I was curious since you asked. I made a sample array and inspected it in Chrome.
var arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
console.log(arr);
Since I already have reference to the array as well as every object I push into it, there's only one other property that could be useful... length. By returning this one additional value of the Array data structure, I now have access to all the relevant information. It seems like the best design choice. That, or return nothing at all if you want to argue for the sake of saving 1 single machine instruction.
Why was it done like this, and is there a historical record of how these decisions came to be made?
No clue - I'm not certain a record of rationale along these lines exists. It would be up to the implementer and is likely commented in any given code base implementing the ECMA script standards.

I don't know "Why was it done like this, and is there a historical record of how these decisions came to be made?".
But I also think it's not clear and not intuitive that push() returns the length of array like below:
let arr = ["a", "b"];
let test = arr.push("c");
console.log(test); // 3
Then, if you want to use clear and intuitive method instead of push(), you can use concat() which returns the array with its values like below:
let arr = ["a", "b"];
let test = arr.concat("c");
console.log(test); // ["a", "b", "c"]

The question is partially answered in the document you mention (Ecma 262 3rd edition), there are methods that mutate the array and methods that don't. The methods that mutate the array will return the length of the mutated array. For adding elements that would be push, splice and unshift (Depending on the position you want the new element in).
If you want to get the new mutated array you can use concat. Concat will input any number of arrays you want added to the original array and add all the elements into a new array. i.e:
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3=['g','h'];
const array4 = array1.concat(array2,array3);
The new array created will have all the elements and the other three won't be changed. There are other (Many) ways to add the elements to an array both mutative and not mutative. So there is your answer, it returns the length because it is changing it, it doesn't need to return the full array.

Related

Does any change in an array gets to change the entire array?

I got into that question by thinking about sorting algorithms.
Does changing an element position inside an array would be the same, at the interpretation, compilation or run-time phases, to recreate the entire array with new elements?
Does it change radically from one language to another?
How does it work?
Imagining a specific case in JavaScript, for example:
let animals = ["monkey","zebra","banana","capybara"];
animals.splice(2,1); // returns ["banana"]
Would it be correct to state that the entire animals array was rewritten? Or is there another type of change type? How would that one and only change work, computation-wise?
It's stated in Javascript documentation that the .splice() method "takes" an array object, creates a new array without it and returns another one with it as an output, but how does it work?
I'm a begginner, so please have patience, and would like a good reading recommendation if possible to learn more about it. Sorry for the incovenience.
Does changing an element position inside an array would be the same, at the interpretation, compilation or run-time phases, to recreate the entire array with new elements?
I think you're asking this:
Does changing an element position inside an array recreate the entire array with new elements?
If you use the array indexer [], then no: the array is not recreated, it's mutated in-place.
If you use Array.prototype.splice, then no: the array is not recreated, it's mutated in-place.
But splice does return a new array containing the removed elements.
If you use Array.prototype.filter and/or Array.prototype.map, then yes: a new array containing only the filtered elements is returned (and the original input array is unmodified at all: i.e. nothing is mutated).
If you use Array.prototype.forEach to assign elements back to itself (which you shouldn't be doing anyway), then no: the array is mutated in-place.
Imagining a specific case in JavaScript, for example:
let animals = ["monkey","zebra","banana","capybara"];
animals.splice(2,1); // returns ["banana"]
Would it be correct to state that the entire animals array was rewritten?
No. That would be incorrect.
The correct thing to say is that "the animals array was mutated".
It's stated in Javascript documentation that the .splice() method "takes" an array object, creates a new array without it and returns another one with it as an output, but how does it work?
I don't know what "JavaScript documentation" you're referring to, but that is incorrect.
The only authoritative sources for JavaScript documentation are either the official ECMAScript specification or documentation provided by JavaScript engine vendors (such as Mozilla's Spidermonkey) documented on MDN.
Sources such as W3Schools are not authoritative.
...and MDN says this about splice (emphasis mine):
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
how does it work?
The inner-workings of the splice function are defined in the ECMAScript specification (section 23.1.3.29).
...which can be summarized as:
Given a splice invocation with arguments ( this: Array, start: int, deleteCount: int );...
Create a new array to hold deleteCount items: deletedItems.
Copy elements in this from index start to start + deleteCount into deletedItems.
Copy elements in this from start + deleteCount to this.length upwards to fill the gap of removed items.
Shrink the length to this.length - deleteCount.
In TypeScript, something like this (ignoring handling of negative arguments):
function splice<T>( this: T[], start: int, deleteCount: int ): T[] {
const removedItems: T[] = [];
const tailIdx = start + deleteCount;
for( let i = start; i < tailIdx; i++ ) {
removedItems.push( this[i] );
}
for( let i = tailIdx, f = start; i < this.length; i++, f++ ) {
this[i] = this[f];
}
this.length = this.length - deleteCount;
}

Why I'm creating an empty object at the end of my array? [duplicate]

Ever since its introduction in ECMA-262, 3rd Edition, the Array.prototype.push method's return value is a Number:
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
What were the design decisions behind returning the array's new length, as opposed to returning something potentially more useful, like:
A reference to the newly appended item/s
The mutated array itself
Why was it done like this, and is there a historical record of how these decisions came to be made?
I understand the expectation for array.push() to return the mutated array instead of its new length. And the desire to use this syntax for chaining reasons.
However, there is a built in way to do this: array.concat().
Note that concat expects to be given an array, not an item. So, remember to wrap the item(s) you want to add in [], if they are not already in an array.
newArray = oldArray.concat([newItem]);
Array chaining can be accomplished by using .concat(), as it returns an array,
but not by .push(), as it returns an integer (the new length of the array).
Here is a common pattern used in React for changing the state variable, based on its prior value:
// the property value we are changing
selectedBook.shelf = newShelf;
this.setState((prevState) => (
{books: prevState.books
.filter((book) => (book.id !== selectedBook.id))
.concat(selectedBook)
}
));
state object has a books property, that holds an array of book.
book is an object with id, and shelf properties (among others).
setState() takes in an object that holds the new value to be assigned to state
selectedBook is already in the books array, but its property shelf needs to be changed.
We can only give setState a top level object, however.
We cannot tell it to go find the book, and look for a property on that book, and give it this new value.
So we take the books array as it were.
filter to remove the old copy of selectedBook.
Then concat to add selectedBook back in, after updating its shelf property.
Great use case for wanting to chain push.
However, the correct way to do this is actually with concat.
Summary:
array.push() returns a number (mutated array's new length).
array.concat([]) returns a new array.
Technically, it returns a new array with the modified element added to the end, and leaves the initial arrays unchanged.
Returning a new array instance, as opposed to recycling the existing array instance is an important distinction, that makes it very useful for state objects in React applications, to get changed data to re-render.
I posted this in TC39's communication hub, and was able to learn a bit more about the history behind this:
push, pop, shift, unshift were originally added to JS1.2 (Netscape 4) in 1997.
There were modeled after the similarly named functions in Perl.
JS1.2 push followed the Perl 4 convention of returning the last item pushed.
In JS1.3 (Netscape 4.06 summer 1998) changed push to follow the Perl 5 conventions of returning the new length of the array.
see original jsarray.c source
/*
* If JS1.2, follow Perl4 by returning the last thing pushed. Otherwise,
* return the new array length.
*/
I cannot explain why they chose to return the new length, but in response to your suggestions:
Returning the newly appended item:
Given that JavaScript uses C-style assignment which emits the assigned value (as opposed to Basic-style assignment which does not) you can still have that behavior:
var addedItem;
myArray.push( addedItem = someExpression() );
(though I recognise this does mean you can't have it as part of an r-value in a declaration+assignment combination)
Returning the mutated array itself:
That would be in the style of "fluent" APIs which gained popularity significantly after ECMAScript 3 was completed and it would not be keeping in the style of other library features in ECMAScript, and again, it isn't that much extra legwork to enable the scenarios you're after by creating your own push method:
Array.prototype.push2 = function(x) {
this.push(x);
return this;
};
myArray.push2( foo ).push2( bar ).push2( baz );
or:
Array.prototype.push3 = function(x) {
this.push(x);
return x;
};
var foo = myArray.push3( computeFoo() );
I was curious since you asked. I made a sample array and inspected it in Chrome.
var arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
console.log(arr);
Since I already have reference to the array as well as every object I push into it, there's only one other property that could be useful... length. By returning this one additional value of the Array data structure, I now have access to all the relevant information. It seems like the best design choice. That, or return nothing at all if you want to argue for the sake of saving 1 single machine instruction.
Why was it done like this, and is there a historical record of how these decisions came to be made?
No clue - I'm not certain a record of rationale along these lines exists. It would be up to the implementer and is likely commented in any given code base implementing the ECMA script standards.
I don't know "Why was it done like this, and is there a historical record of how these decisions came to be made?".
But I also think it's not clear and not intuitive that push() returns the length of array like below:
let arr = ["a", "b"];
let test = arr.push("c");
console.log(test); // 3
Then, if you want to use clear and intuitive method instead of push(), you can use concat() which returns the array with its values like below:
let arr = ["a", "b"];
let test = arr.concat("c");
console.log(test); // ["a", "b", "c"]
The question is partially answered in the document you mention (Ecma 262 3rd edition), there are methods that mutate the array and methods that don't. The methods that mutate the array will return the length of the mutated array. For adding elements that would be push, splice and unshift (Depending on the position you want the new element in).
If you want to get the new mutated array you can use concat. Concat will input any number of arrays you want added to the original array and add all the elements into a new array. i.e:
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3=['g','h'];
const array4 = array1.concat(array2,array3);
The new array created will have all the elements and the other three won't be changed. There are other (Many) ways to add the elements to an array both mutative and not mutative. So there is your answer, it returns the length because it is changing it, it doesn't need to return the full array.

how is this function mutating non-returned array

I'd ask a better question, but I don't don't know how. Thanks for your help.
***ISSUE: I'm sending array vari to a function and it's coming back changed even though I didn't return it or even use same variable name. Desired function: variable vari does not change
I've logged the function and isolated the change to the [].forEach() statement below, noted with ***. I send vari but return varis and assign to new variable sum. How does this change the vari variable?
//this is what calls the sub function using vari, an array, 411.0, 13.0
var sum = doSum1(vari);
function doSum1(vari0) {
// called from doSum1
// grab Variance data // ALL COLUMNS FOR NOW // fix loc/stat columns below
var vstat = vari0[0].indexOf('Status');
vari1 = vari0.filter(r=>r[vstat]); // to ensure indexOf works, and speed processing
var vhdr = ['Campaign ID','Campaign','Site ID','Site','Placement','Placement ID','Date','DCM Imp','Upw Imp','Tag Location','Status','Site Count','Site Imp'];
// move loc and status over in place of variance and percent (loc/stat will be site ct/imp)
varis=[];
// *** THIS FOREACH CHANGES varis AND vari. Not sure how... see more specifically below
['Not Tracking','Undertracking','Overtracking','Absent in DCM'].forEach(rf=>{
varis.push(vhdr.map(r=>''));
varis[varis.length-1][0]=rf;
varis.push(vhdr);
if(vari1.filter(r=>r[vstat].indexOf(rf)>=0).length==0) {
varis.push(vhdr.map(r=>''));
varis[varis.length-1][0]='none found';
} else {
varis.push(vari1.filter(r=>r[vstat].toString().indexOf(rf)>=0)[0]); // break out of outer []
//fix loc/stat location
//*** MORE SPECIFICALLY, this line in particular changes in vari, not just varis as intended.
varis[varis.length-1].splice(9,4,varis[varis.length-1][11],varis[varis.length-1][12],'','')
}
varis.push(vhdr.map(r=>'')); // trailing blank line
});
return varis;
I tried this in place of the splice as well, but same result... just not sure how varis is changing vari...
varis[varis.length-1][9] = varis[varis.length-1][11];
varis[varis.length-1][10] = varis[varis.length-1][12];
varis[varis.length-1][11] = '';
varis[varis.length-1][12] = '';
vari is a 2D array. That means that every element in vari is an array as well, and as such passed by reference and subject to mutation.
The Array.splice() method mutates its argument array. In the code, each varis[varis.length-1].splice() call modifies an array object that is copied from vari1 by reference, and therefore also vari0 whose elements are array objects that are copied to vari1 by reference. This is what causes vari to mutate.
To avoid the issue, use one these patterns:
var vari1 = vari0.map(row => row.slice()).filter(r => r[vstat]);
or
var vari1 = vari0.map(row => row.map(value => value)).filter(r => r[vstat]);
The patterns use Array.map() and Array.slice()to get a shallow copy of the 2D array referenced by vari0 (i.e., vari).
The first map() creates a new array of that contains the rows of vari0. The rows are arrays and therefore mutable, so a slice() or another map() is required to copy the rows into new arrays as well.
Note that the copy is shallow, which means that only primitive values such as text strings and numbers are copied by value. Your comments indicate that the rows of vari only contain primitives, so the pattern will make a copy that is safe to modify and will not mutate vari. Were the rows of the vari 2D array contain yet more arrays or other objects, the would be copied by reference and therefore still be subject to mutation.
Note that Array.splice() and Array.slice() are very different from each other. The Array.splice() method mutates its argument array. The Array.slice() method creates a shallow copy of the array, and is in fact often used to safely copy 1D arrays that contain primitives. In your use case, the vari array does not contain primitives but arrays, so we need to call slice() within map() to copy the primitive values in the second level of the 2D array.
In the general case, deep cloning an array or another object is surprisingly complex. The patterns above are probably the simplest way to do it in your use case. See What is the most efficient way to deep clone an object in JavaScript?

Why does Array.prototype.push return the new length instead of something more useful?

Ever since its introduction in ECMA-262, 3rd Edition, the Array.prototype.push method's return value is a Number:
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
What were the design decisions behind returning the array's new length, as opposed to returning something potentially more useful, like:
A reference to the newly appended item/s
The mutated array itself
Why was it done like this, and is there a historical record of how these decisions came to be made?
I understand the expectation for array.push() to return the mutated array instead of its new length. And the desire to use this syntax for chaining reasons.
However, there is a built in way to do this: array.concat().
Note that concat expects to be given an array, not an item. So, remember to wrap the item(s) you want to add in [], if they are not already in an array.
newArray = oldArray.concat([newItem]);
Array chaining can be accomplished by using .concat(), as it returns an array,
but not by .push(), as it returns an integer (the new length of the array).
Here is a common pattern used in React for changing the state variable, based on its prior value:
// the property value we are changing
selectedBook.shelf = newShelf;
this.setState((prevState) => (
{books: prevState.books
.filter((book) => (book.id !== selectedBook.id))
.concat(selectedBook)
}
));
state object has a books property, that holds an array of book.
book is an object with id, and shelf properties (among others).
setState() takes in an object that holds the new value to be assigned to state
selectedBook is already in the books array, but its property shelf needs to be changed.
We can only give setState a top level object, however.
We cannot tell it to go find the book, and look for a property on that book, and give it this new value.
So we take the books array as it were.
filter to remove the old copy of selectedBook.
Then concat to add selectedBook back in, after updating its shelf property.
Great use case for wanting to chain push.
However, the correct way to do this is actually with concat.
Summary:
array.push() returns a number (mutated array's new length).
array.concat([]) returns a new array.
Technically, it returns a new array with the modified element added to the end, and leaves the initial arrays unchanged.
Returning a new array instance, as opposed to recycling the existing array instance is an important distinction, that makes it very useful for state objects in React applications, to get changed data to re-render.
I posted this in TC39's communication hub, and was able to learn a bit more about the history behind this:
push, pop, shift, unshift were originally added to JS1.2 (Netscape 4) in 1997.
There were modeled after the similarly named functions in Perl.
JS1.2 push followed the Perl 4 convention of returning the last item pushed.
In JS1.3 (Netscape 4.06 summer 1998) changed push to follow the Perl 5 conventions of returning the new length of the array.
see original jsarray.c source
/*
* If JS1.2, follow Perl4 by returning the last thing pushed. Otherwise,
* return the new array length.
*/
I cannot explain why they chose to return the new length, but in response to your suggestions:
Returning the newly appended item:
Given that JavaScript uses C-style assignment which emits the assigned value (as opposed to Basic-style assignment which does not) you can still have that behavior:
var addedItem;
myArray.push( addedItem = someExpression() );
(though I recognise this does mean you can't have it as part of an r-value in a declaration+assignment combination)
Returning the mutated array itself:
That would be in the style of "fluent" APIs which gained popularity significantly after ECMAScript 3 was completed and it would not be keeping in the style of other library features in ECMAScript, and again, it isn't that much extra legwork to enable the scenarios you're after by creating your own push method:
Array.prototype.push2 = function(x) {
this.push(x);
return this;
};
myArray.push2( foo ).push2( bar ).push2( baz );
or:
Array.prototype.push3 = function(x) {
this.push(x);
return x;
};
var foo = myArray.push3( computeFoo() );
I was curious since you asked. I made a sample array and inspected it in Chrome.
var arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
console.log(arr);
Since I already have reference to the array as well as every object I push into it, there's only one other property that could be useful... length. By returning this one additional value of the Array data structure, I now have access to all the relevant information. It seems like the best design choice. That, or return nothing at all if you want to argue for the sake of saving 1 single machine instruction.
Why was it done like this, and is there a historical record of how these decisions came to be made?
No clue - I'm not certain a record of rationale along these lines exists. It would be up to the implementer and is likely commented in any given code base implementing the ECMA script standards.
I don't know "Why was it done like this, and is there a historical record of how these decisions came to be made?".
But I also think it's not clear and not intuitive that push() returns the length of array like below:
let arr = ["a", "b"];
let test = arr.push("c");
console.log(test); // 3
Then, if you want to use clear and intuitive method instead of push(), you can use concat() which returns the array with its values like below:
let arr = ["a", "b"];
let test = arr.concat("c");
console.log(test); // ["a", "b", "c"]
The question is partially answered in the document you mention (Ecma 262 3rd edition), there are methods that mutate the array and methods that don't. The methods that mutate the array will return the length of the mutated array. For adding elements that would be push, splice and unshift (Depending on the position you want the new element in).
If you want to get the new mutated array you can use concat. Concat will input any number of arrays you want added to the original array and add all the elements into a new array. i.e:
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3=['g','h'];
const array4 = array1.concat(array2,array3);
The new array created will have all the elements and the other three won't be changed. There are other (Many) ways to add the elements to an array both mutative and not mutative. So there is your answer, it returns the length because it is changing it, it doesn't need to return the full array.

What use does the JavaScript forEach method have (that map can't do)?

The only difference I see in map and foreach is that map is returning an array and forEach is not. However, I don't even understand the last line of the forEach method "func.call(scope, this[i], i, this);". For example, isn't "this" and "scope" referring to same object and isn't this[i] and i referring to the current value in the loop?
I noticed on another post someone said "Use forEach when you want to do something on the basis of each element of the list. You might be adding things to the page, for example. Essentially, it's great for when you want "side effects". I don't know what is meant by side effects.
Array.prototype.map = function(fnc) {
var a = new Array(this.length);
for (var i = 0; i < this.length; i++) {
a[i] = fnc(this[i]);
}
return a;
}
Array.prototype.forEach = function(func, scope) {
scope = scope || this;
for (var i = 0, l = this.length; i < l; i++) {
func.call(scope, this[i], i, this);
}
}
Finally, are there any real uses for these methods in JavaScript (since we aren't updating a database) other than to manipulate numbers like the following?
alert([1,2,3,4].map(function(x){ return x + 1})); // This is the only example I ever see of map in JavaScript.
The essential difference between map and forEach in your example is that forEach operates on the original array elements, whereas map explicitly returns a new array as a result.
With forEach you are taking some action with -- and optionally changing -- each element in the original array. The forEach method runs the function you provide for each element, but returns nothing (undefined). On the other hand, map walks through the array, applies a function to each element, and emits the result as a new array.
The "side effect" with forEach is that the original array is being changed. "No side effect" with map means that, in idiomatic usage, the original array elements are not changed; the new array is a one-to-one mapping of each element in the original array -- the mapping transform being your provided function.
The fact that there's no database involved does not mean that you won't have to operate on data structures, which, after all, is one of the essences of programming in any language. As for your last question, your array can contain not only numbers, but objects, strings, functions, etc.
The main difference between the two methods is conceptual and stylistic: You use forEach when you want to do something to or with each element of an array (doing "with" is what the post you cite meant by "side-effects", I think), whereas you use map when you want to copy and transform each element of an array (without changing the original).
Because both map and forEach call a function on each item in an array, and that function is user-defined, there is almost nothing you can do with one and not with the other. It's possible, though ugly, to use map to modify an array in-place and/or do something with array elements:
var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.map(function(el) {
el.val++; // modify element in-place
alert(el.val); // do something with each element
});
// a now contains [{ val: 2 }, { val: 3 }, { val: 4 }]
but much cleaner and more obvious as to your intent to use forEach:
var a = [{ val: 1 }, { val: 2 }, { val: 3 }];
a.forEach(function(el) {
el.val++;
alert(el.val);
});
Especially if, as is usually the case in the real world, el is a usefully human-readable variable:
cats.forEach(function(cat) {
cat.meow(); // nicer than cats[x].meow()
});
In the same way, you can easily use forEach to make a new array:
var a = [1,2,3],
b = [];
a.forEach(function(el) {
b.push(el+1);
});
// b is now [2,3,4], a is unchanged
but it's cleaner to use map:
var a = [1,2,3],
b = a.map(function(el) {
return el+1;
});
Note as well that, because map makes a new array, it likely incurs at least some performance/memory hit when all you need is iteration, particularly for large arrays - see http://jsperf.com/map-foreach
As for why you'd want to use these functions, they're helpful any time you need to do array manipulation in JavaScript, which (even if we're just talking about JavaScript in a browser environment) is pretty often, almost any time you're accessing an array that you're not writing down by hand in your code. You might be dealing with an array of DOM elements on the page, or data pulled from an Ajax request, or data entered in a form by the user. One common example I run into is pulling data from an external API, where you might want to use map to transform the data into the format you want and then use forEach to iterate over your new array in order to display it to your user.
The voted answer (from Ken Redler) is misleading.
A side effect in computer science means that a property of a function/method alters a global state [Wikipedia]. In some narrow sense, this may also include reading from a global state, rather than from arguments. In imperative or OO programming, side effects appear most of the time. And you are probably making use of it without realizing.
The significant difference between forEach and map is that map allocates memory and stores the returning value, while forEach throws it away. See the ECMA specification for more information.
As for the reason why people say forEach is used when you want a side effect is that the return value of forEach is always undefined. If it has no side effect (does not change global state), then the function is just wasting CPU time. An optimizing compiler will eliminate this code block and replace the it with the final value (undefined).
By the way, it should be noted that JavaScript has no restriction on side effects. You can still modify the original array inside map.
var a = [1,2,3]; //original
var b = a.map( function(x,i){a[i] = 2*x; return x+1} );
console.log("modified=%j\nnew array=%j",a,b);
// output:
// modified=[2,4,6]
// new array=[2,3,4]
This is a beautiful question with an unexpected answer.
The following is based on the official description of Array.prototype.map().
There is nothing that forEach() can do that map() cannot. That is, map() is a strict super-set of forEach().
Although map() is usually used to create a new array, it may also be used to change the current array. The following example illustrates this:
var a = [0, 1, 2, 3, 4], mapped = null;
mapped = a.map(function (x) { a[x] = x*x*x; return x*x; });
console.log(mapped); // logs [0, 1, 4, 9, 16] As expected, these are squares.
console.log(a); // logs [0, 1, 8, 27, 64] These are cubes of the original array!!
In the above example, a was conveniently set such that a[i] === i for i < a.length. Even so, it demonstrates the power of map(), and in particular its ability to change the array on which it is called.
Note1:
The official description implies that map() may even change length the array on which it is called! However, I cannot see (a good) reason to do this.
Note 2:
While map() map is a super-set of forEach(), forEach() should still be used where one desires the change a given array. This makes your intentions clear.
You can use map as though it were forEach.
It will do more than it has to, however.
scope can be an arbitrary object; it's by no means necessarily this.
As for whether there are real uses for map and forEach, as well to ask if there are real uses for for or while loops.
While all the previous questions are correct, I would definitely make a different distinction. The use of map and forEach can imply intent.
I like to use map when I am simply transforming the existing data in some way (but want to make sure the original data is unchanged).
I like to use forEach when I am modifying the collection in place.
For instance,
var b = [{ val: 1 }, { val: 2 }, { val: 3 }];
var c = b.map(function(el) {
return { val: el.val + 1 }; // modify element in-place
});
console.log(b);
// [{ val: 1 }, { val: 2 }, { val: 3 }]
console.log(c);
// [{ val: 3 }, { val: 4 }, { val: 5 }]
My rule of thumb being making sure when you map you are always creating some new object/value to return for each element of the source list and returning it rather than just performing some operation on each element.
Unless you have any real need to modify the existing list, it doesn't really make sense to modify it in place and fits better into functional/immutable programming styles.
TL;DR answer --
map always returns another array.
forEach does not. It is up to you to decide what it does. Return an array if you want or do something else if you don't.
Flexibility is desirable is certain situations. If it isn't for what you are dealing with then use map.
Others have already posted about your main question regarding the difference between the functions. But for...
are there any real uses for these methods in JavaScript (since we aren't updating a database) other than to manipulate numbers like this:
...it's funny you should ask. Just today I wrote a piece of code that assigns a number of values from a regular expression to multiple variables using map for transformation.
It was used to convert a very complicated text-based structure into visualizable data ... but for simplicity's sake, I shall offer an example using date strings, because those are probably more familiar for everyone (though, if my problem had actually been with dates, instead of map I would've used Date-object, which would've done the job splendidly on its own).
const DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
const TEST_STRING = '2016-01-04T03:20:00.000Z';
var [
iYear,
iMonth,
iDay,
iHour,
iMinute,
iSecond,
iMillisecond
] = DATE_REGEXP
// We take our regular expression and...
.exec(TEST_STRING)
// ...execute it against our string (resulting in an array of matches)...
.slice(1)
// ...drop the 0th element from those (which is the "full string match")...
.map(value => parseInt(value, 10));
// ...and map the rest of the values to integers...
// ...which we now have as individual variables at our perusal
console.debug('RESULT =>', iYear, iMonth, iDay, iHour, iMinute, iSecond, iMillisecond);
So ... while this was just an example - and only did a very basic transformation for the data (just for sake of example) ... having done this without map would've been a much more tedious task.
Granted, it is written in a version of JavaScript that I don't think too many browsers support yet (at least fully), but - we're getting there. If I needed to run it in browser, I believe it would transpile nicely.

Categories

Resources