0

To map an array of stringified numbers to actual Numbers, I can simply pass the Number function:

let arr = ["0", "1", "-2.5"];

console.log(arr.map(Number));

Now I would like to use the same approach with document.getElementById to map a list of id strings to their corresponding DOM nodes:

let arr = ["a", "b"];

console.log(arr.map(document.getElementById));
<div id="a">a <span id="b">b</span></div>

which gives me

"TypeError: 'getElementById' called on an object that does not implement interface Document."

Can someone explain the error?

connexo
  • 53,704
  • 14
  • 91
  • 128
  • It's the same issue as in [Passing scope to forEach](https://stackoverflow.com/questions/19733758/passing-scope-to-foreach). – trincot May 25 '22 at 14:18
  • BTW, the first parameter of `.map` is not a predicate (as if its truthfulness would matter), but a mapper. – trincot May 25 '22 at 14:41

2 Answers2

1

You can find an explanation of what is happening here:

When this error is thrown, a function (on a given object), is called with a this not corresponding to the type expected by the function.

This issue can arise when using the Function.prototype.call() or Function.prototype.apply() methods, and providing a this argument which does not have the expected type.

This issue can also happen when providing a function that is stored as a property of an object as an argument to another function. In this case, the object that stores the function won't be the this target of that function when it is called by the other function. To work-around this issue, you will either need to provide a lambda which is making the call, or use the Function.prototype.bind() function to force the this argument to the expected object.

I also add my alternative solution: there is an overload of the method map that allows you to set the context in the second parameter:

let arr = ["a", "b"];

console.log(arr.map(document.getElementById, document));
<div id="a">a <span id="b">b</span></div>
connexo
  • 53,704
  • 14
  • 91
  • 128
0

Experimenting, it seems to be related to this no longer pointing to the correct context (which would be document) when passing document.getElementById as a callback to map, so explicitly passing a bound getElementById does the trick:

let arr = ["a", "b"];

console.log(arr.map(document.getElementById.bind(document)));
<div id="a">a <span id="b">b</span></div>

Unfortunately, this also kind of defeats the purpose of the approach, which would be conciseness, as this can be shortened from

arr.map(document.getElementById.bind(document))
// to
arr.map(id=>document.getElementById(id))
connexo
  • 53,704
  • 14
  • 91
  • 128
  • `arr.map(x => document.getElementById(x)` also works. – Andy May 25 '22 at 14:03
  • @Andy Sure, as would `arr.map(n=>Number(n))`. But in case of Number it's just so much more concise. – connexo May 25 '22 at 14:03
  • 2
    *"`this` losing the correct context"*: that is a strange way of putting it. There is nothing that loses anything here. `this` is determined at the moment of the call, and if the caller does not do `document.getElementById(...)`, but does `callback(...)`, then there is no `this` argument passed with that call. – trincot May 25 '22 at 14:05
  • @Trincot I'd be thankful if you added a more precise answer. I'd delete my own then. – connexo May 25 '22 at 14:07