Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does 'this' keyword work in map() and call()?

I have been refreshing my JavaScript knowledge of call() and map() usage on NodeList.

It was fairly easy to google out, what call() should be doing and there are resources with examples of how it works with map().

However, as I noticed on MDN, the map() function can also take a second argument, which should be setting the this keyword for map() - or at least that is what I think it should be doing.

I have tried to check it myself with simple arrays:

var numbers = [1, 2, 3];
var letters = ['a', 'b', 'c'];

..and with a simple function, that is about to be given as a parameter to map():

var effector = function (x) {
    console.log(x);
}

Now, what I do not understand, is why these two function calls have different results:

numbers.map(effector, letters);
numbers.map.call(letters, effector);

I expect them to both output letters to the console, as both this keywords should be referencing to them (respectively to their objects).

I did some further research, and tried with this modified effector function:

var effector = function () {
    console.log(this);
}

..again on:

numbers.map(effector, letters);
numbers.map.call(letters, effector);

Assuming, that we are in "use strict", the first call logs letters and the second logs undefined.

But again, I would expect both these calls to produce the same output.

What am I missing?

EDIT:

I was reading, how .map can be polyfilled, if you check it on MDN, there you see, how the second parameter of .map is used in callback.call().

I suppose, that in the end, even that callback.call() should have the same this as was in .map.

MDN - map

like image 454
Martin Melichar Avatar asked Oct 12 '25 11:10

Martin Melichar


2 Answers

There are two bindings of the this reference at play here:

  • this for the execution context of the map function
  • this for the execution context of the callback function

They don't relate to each other.

The second argument of map dictates what this will be for the callback. If it is not provided, the default is undefined (not the array).

The first argument of map.call dictates what this will be for map -- and by consequence which array will be iterated.

This is also reflected in the polyfill provided on mdn: it is perfectly in line with these specifications: O gets the value of this for the map function, and T gets the value of this for the callback. They are generally different.

map versus forEach

Unrelated to your question, but worth mentioning: don't use map when you are not actually mapping anything. map is intended for creating a new array, one in which every value has been mapped by calling the callback function on the original value at that same index.

When however you just need to iterate the array values, without any intent to perform such mapping, then use the forEach method, or a for...of loop. Both these work on NodeList out of the box, without the need to .call.

like image 114
trincot Avatar answered Oct 13 '25 23:10

trincot


The difference between numbers.map(effector, letters) and numbers.map.call(letters, effector) lies in which function the this is set to letters.

In the first, as MDN explains, the second argument is the value to use as this inside the callback - ie the argument supplied to map. That is, in numbers.map(effector, letters), the this inside effector will be letters. Since effector (in the first version) doesn't care what this is, that argument essentially has no effect, and the contents of numbers are logged.

In numbers.map.call(letters, effector), on the other hand, it is numbers.map that has its this set to letters. Which effectively means the map method, although called on numbers, is "hijacked" into logging the entries of letters instead.

The second example works similarly - the crucial thing is which function the this is set in: effector in one case, numbers.map in the other.

like image 45
Robin Zigmond Avatar answered Oct 13 '25 23:10

Robin Zigmond