I'm learning JavaScript. I wrote this code to learn the map function. But then I got confused as to why is this not mapping over it continuously as with each map sequence a new element is pushed to the array. Shouldn't it continue to push new elements as it is mapping over ? Why does the map function only run for the original three elements and not for the new pushed ones?
I tried to debug it in the node environment and the arr variable goes in a closure. I know what a closure but I'm not able to understand what is going on here.
let array = [1, 2, 3];
array.map((element) => {
array.push(10);
console.log(element);
});
I expect that the output should be 1,2,3,10,10,10,10,10,10,10,10......10
But the actual output is only 1,2,3.
To quote from MDN:
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, their value as passed to callback will be the value at the time map visits them.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Description
So, it behaves like that because that is how it is designed. And it is designed that way, amongst other reasons, to prevent infinite loops!
map is a function from the Functional Programming world, where immutability is an important principle. According to this principle, if you call map on an input (and other variables don't change) you will always get exactly the same result. Allowing modification of the input breaks immutability.
Because it does not mutate the array (see Array.prototype.map()).
Instead, it returns a new array with the results of calling a provided function on every element in the calling array.
In the following snippet, what mutates the array is the call to array.push(10); three times (once per element in the original array) and not the map function itself.
let newArray = array.map((element) => {
array.push(10); // <-- here you mutate the array
console.log(element);
});
An important quote from the mentioned documentation (and key point here is):
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.
In the following snippet you can see an example of how to properly use the map function:
let array = [1,2,3];
let newArray = array.map(element => element + 10); // sum 10 to every element
console.log('original: ', array); // original: [1,2,3]
console.log('new one: ', newArray) // new one: [11,12,13]
One last thought (from the docs too), taking as reference the code you posted:
Since map builds a new array, using it when you aren't using the returned array is an anti-pattern; use forEach or for-of instead.
Signs you shouldn't be using map:
A) You're not using the array it returns, and/or
B) You're not returning a value from the callback.
Why is exactly as seen on MDN:
var new_array = arr.map(function callback(currentValue[, index[, array]]){}
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.
(Thank you to Joe's post above from MDN for this quote.)
Once map is called it then takes the array at that moment as it's parameter; once it's been passed then any changes are irrelevant to the previous variable itself.
See below:
let array = [1, 2, 3];
array.map((element) => {
array.push(10);
console.log(element);
});
console.log(array)
Related
I'm learning Svelte, and read in the documentation that arrays need to be reassigned in order for a component or page to update it. For that they devised a more idiomatic solution. Instead of writing:
messages.push('hello');
messages = messages;
you can write instead:
messages = [...messages, 'hello'];
Alright, makes sense. But then the documentation says:
You can use similar patterns to replace pop, shift, unshift and splice.
But how? I cannot see how you can remove items from an array. More to the point, how could I write the following more idiomatically?
messages.splice(messages.indexOf('hello'), 1);
messages = messages;
You could e.g. use the filter array method to create a new array without the element 'hello':
messages = messages.filter(m => m !== 'hello');
As mentioned, Svelte's reactivity is triggered by assignments. The current Svelte tutorial uses JavaScript's (ES6) spread syntax (three dots) to add the next-higher number to an array, providing a more idiomatic solution than a redundant assignment using push:
function pushNumber() {
numbers = [...numbers, lastnumber]; // 1, 2, 3, 4, 5
}
You could use spread syntax to replace pop, shift, unshift and splicethough it might increase the time and complexity of the operation in some cases:
function unshiftNumber() {
numbers = [firstnumber, ...numbers]; // 0, 1, 2, 3, 4
}
function popNumber() {
numbers = [...numbers.slice(0,numbers.length - 1)]; // 1, 2, 3
}
function shiftNumber() {
numbers = [...numbers.slice(1,numbers.length)]; // 2, 3, 4
}
function spliceNumber() {
numbers = [firstnumber, ...numbers.slice(0,numbers.length-1)];// 0, 1, 2, 3
}
Spread is just one way to do it, though. The purpose behind not using pop/push etc is to encourage immutability. So any removal can just be a filter, for example.
There are several things to consider here.
Given this code:
messages.splice(messages.indexOf('hello'), 1);
messages = messages;
What's happening here is:
Looking for the first occurrence of the string "hello" in the array
Removing such element from the array, based on the index found.
The assumption here is that "hello" needs to exists, otherwise the could would remove the last item from the array (since indexOf returns -1).
The original array is therefore mutate: depends by the context, that sometimes can be preferable instead of copying the whole array into a new one; otherwise it's generally a better practice avoid such mutation.
So. If you want to have this behavior exactly, probably this is the best code you can have. For example, takes the filter example:
messages = messages.filter(message => message !== "hello")
What's happening here is:
Filter out any element equals to "hello"
Returns a new array without such element
So it's quite different from the original code: first of all, it always loop the whole array. If you have thousands of element, even if you have only one "hello" at the second index, it would always iterate all of them. Maybe it's what you want, maybe not. If the element is unique, such as an id, maybe you want to stop once you find it.
Second, it returns a new array. Again, that usually a better practice than mutate the array, but in some context it's preferable mutate it instead of create a new one.
So, if you want to mutate the original array, it's probably better to stick to your original code.
If, instead, you don't care (such as the example of push), I believe that in the intention of svelte's developers, your code would be roughly translate to:
let i = messages.indexOf("hello");
messages = [...messages.slice(0, i), ...messages.slice(i + 1)];
(Still assuming there is a "hello" message and you're interested only in the first occurrence).
It's unfortunate that JS doesn't have a better syntax to handles slices.
In case you're wandering, filter can also be used to remove elements using a given index:
let elements = ['a','b', 'c'];
let idx = 1;
elements = elements.filter( (e,i) => i !== idx );
// => ['a', 'c']
You can perform the usual push and pop or `splice on your Array
But because Svelte's reactivity is triggered by assignments, using array methods like push and splice won't automatically cause updates.
According to All about Immutable Arrays and Objects in JavaScript you can do it this way...
let messages = ['something', 'another', 'hello', 'word', 'another', 'again'];
const indexOfHello = messages.indexOf('hello');
messages = [...messages.slice(0, indexOfHello), ...messages.slice(indexOfHello + 1)];
Note the difference between splice and slice
The splice() method adds/removes items to/from an array, and returns
the removed item(s). Note: This method changes the original array.
Syntax: array.splice(start, deleteCount, itemstoAdd, addThisToo);
But
The slice() method returns the selected elements in an array, as a new array object. The slice() method selects the elements starting at the given start argument, and ends at, but does not include, the given end argument.
Note: The original array will not be changed.
In order words
It return a shallow copy of a portion of an array into a new array
object selected from begin to end (end not included). The original
array will not be modified.
Syntax: array.slice(start, end);
You can try this: https://svelte.dev/repl/0dedb37665014ba99e05415a6107bc21?version=3.53.1
use a library called svelox. It allows you to use the Array native api(push/splice...etc.) without reassignment statements.
Spread the spliced array to reassign it to itself ;)
messages = [...messages.splice(messages.indexOf('hello'), 1)];
The goal is to make Svelte detecting that array messages (a property of your component or a variable in the Svelte store) has changed. This is why the array messages must be declared with let or var keyword, not const. This way you're allowed to reassign it. And the reassign operation itself is sufficient to make Svelte detecting that the array has changed.
Perhaps even, simply by doing so works too:
messages = messages.splice(messages.indexOf('hello'), 1);
I am having an array of objects where all objects have the same keys except the last object. Think like array have values and to denote all these values as a whole I have a key I am pushing the key at last along with the values in the array.
homeTask is a list of object which is the values and homeTaskKey is the key to represent the homeTask
res.data.resultSet.homeTask.forEach(element => {
var singleEvent={
task:'',
taskDuration:'',
status:'',
};
singleEvent.task=element.task;
singleEvent.taskDuration=element.taskDuration;
singleEvent.status=element.status;
newEvents.push(singleEvent);
});
newEvents.push(res.data.resultSet.homeTaskKey);
addEvent(newEvents);
}
addEvent is props method of parent component where I am setting the array to state variable name as events which is array type.
When I iterate over events using map I want to skip the last object since it does not have keys like task, taskDuration and status. Therefore it won't give any problem when I fetch those values.
events.slice(0, events.length-1).map(<function>);
this will ignore the last element and all n-1 entries will be fed to map
UPDATE:
the array name is events not event therefore it should be events.length
You could still use map, but simply pop the last element off once the map completes. For example:
const newEvents = homeTask.map(({ task, taskDuration, status }) => ({
task, taskDuration, status
}))
newEvents.pop()
addEvent(newEvents)
Or just replace the last item with your taskkey, as you know the last item will be junk:
newEvents[newEvents.length - 1] = res.data.resultSet.homeTaskKey
Or just slice the array prior to mapping, and then push the taskKey at the end like you were doing. slice is safe to perform on your prop, as it shallow copies.
Or most importantly, ask yourself why you have this entirely weird data structure that has the key appended on the end. Perhaps rethink your data and not create this problem for yourself in the first place.
res.data.resultSet.homeTask.forEach((element,index) => {})
second param to function is index you, can use this to identify the second last element by comparing it with total length of array.
hmm you can try with this
res.data.resultSet.homeTask.forEach(element => {
if(!element.task)
return false;
...bla bla bla
}
The map() method creates a new array with the results of calling a function for every array element.
So it creates an array of same length from source array.
What you need is filter()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Something like this;
const tasks = res.data.resultSet.homeTask.filter((element) => {
const { task, taskDuration, status } = element;
return task && taskDuration && status;
});
I have an algorithm :
let someArray = [1, 2, 3, 4, 5]
let mapped = someArray.map(number => {
let index = someArray.indexOf(5)
if (index !== -1) {
someArray.splice(index, 1)
}
console.log(typeof number)
return number
})
console.log(mapped)
console.log(mapped.length)
console.log(Object.keys(mapped).length)
So what I expected to have was mapped=[1,2,3,4] and mapped.length=4
But instead I have mapped=[1,2,3,4,empty] and mapped.length=5.
So what I thought is : in the beginning, map is going for 5 iterations so it does it no matter what. That's why I added console.log(typeof number).
But it's executed only 4 times.
I know to have my expected result, filter is way better. I'm just wondering, what is happening here ?
See MDN documentation:
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).
If existing elements of the array are changed, their value as passed to callback will be the value at the time map visits them. Elements that are deleted after the call to map begins and before being visited are not visited.
You're mutating the array as you're iterating over it, which means that once index [4] is reached, that element (whose value used to be 5) doesn't exist anymore, which means the function does not get called on that iteration, resulting in <empty>. The resulting array is created with 5 elements, but the callback is never called on the last element.
Use filter instead.
I've found that the native js sort function screws up from time to time so I wanted to implement my own. Lets say i have the following :
Array.prototype.customSort = function(sortFunction, updated) {...}
var array = [5, 2, 3, 6]
array.customSort(function(a,b) {return a - b})
console.log(array)
Array should be [2, 3, 5, 6]
Updated is the array that has been sorted.
No matter what I return in customSort, the order of array is still in the original order. How do I overwrite the 'this' value / get it to point to the array with the correct order?
If you consider the actual code you gave above, you have to make sure that your customSort function updates this.
One case is that customSort only uses this as "read-only" input, that is - only puts the sorted array in updated, rather than changing this.
In such case, considering the code above (which you might have performed tests with), no updated parameter is sent to the function, to receive the sorted values.
Another case is that customSort returns the sorted array, in which case you have to collect it:
array = array.customSort(function(a,b) {return a - b});
console.log(array);
I just ended up iterating over the updated array and replacing each value in this with the value in updated. In code, that looks like...
function customSort(cb) {
...//updated is the sorted array that has been built
var that = this;
_.each(updated, function (ele, index) {
that[index] = ele;
})
}
I wanted the function to operate in the exact same way the native array.sort function does - it overwrites the array provided instead of returning a new, sorted array.
I find it odd that this works... you cannot overwrite the entire this value in one clean sweep but you can in steps. I couldn't do this for in the customSort function :
this = updated;
The map function looks much better than a for loop:
var arr = ["Apple", "Orange"];
// map example
arr.map(function(item){ eat(item); });
// for example
for(var i=0; i<arr.length; i++){
eat(arr[i]);
}
And I would prefer a map over for but:
A function, which map becomes as an argument should have a return value to be reduces to an array which map should return. What impact has passing a viod function to a map, like in my example?
map returns an array. What happens if I don't assign it to a variable, like in my example?
Is there any void function like map?
A function, which map becomes as an argument should have a return value to be reduces to an array which map should return. What impact has passing a viod function to a map, like in my example?
The value returned by a function without a return statement is undefined. Thus, if you pass a function that never returns anything to map, it will create a new array with length equal to your original array and each value being undefined.
map returns an array. What happens if I don't assign it to a variable, like in my example?
An array is still created but it will be discarded immediately after creation and garbage collected shortly. This behavior might be optimized away by the JS engine!
Is there any void function like map?
As everyone else pointed out, there is the Array.prototype.forEach function which iterates over the array (similarly to map) but doesn't create a new array or use the result of the callback associated to it in any way.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
You're looking for .forEach().
You may find that browsing the MDN documentation is a better way to accumulate this sort of information than asking questions. For now at least I hope this answer will assuage your suffering.
edit — as to question 1, it won't hurt anything. The .map() function will build an array full of undefined entries. (I think.)
For 2, it's also OK. It is never a problem to ignore the return value of a function, at least as far as the runtime is concerned.
1. A function, which map becomes as an argument should have a return value to be reduces to an array which map should return. What impact has passing a viod function to a map, like in my example?
var numbers = [1, 4, 9];
var mapped = numbers.map(function(){
//do nothing
});
console.log(numbers);//[1, 4, 9]
console.log(mapped);//[undefined, undefined, undefined]
the initial array will remain as same and you will get an array with undefined array
2 . map returns an array. What happens if I don't assign it to a variable, like in my
example?
nothing will happen, you loose the changed array.
3.Is there any void function like map?
All array methods doesnot modify the reference array ,
EG
Array.concat(value1[value2[value...]])
Array.every(function)
Array.filter(function)
Array.forEach(function)
Array.join(delimeter)
Array.indexOf(searchStr[, startIndex])
Array.lastIndexOf(searchStr[, startIndex])
Array.map(function) .... etc
but Array.splice(), push, pop, shift, unshift,.. etc will change
What you're looking for is the forEach method.
var arr = ["Apple", "Orange"];
arr.forEach(eat);
There's the forEach array instance method.