Say I have 3 <img>
tags on a page, and I would like to get these as an array, so I wrote:
let myArray = ['img1', 'img2', 'img3'].map(id => document.getElementById(id));
... which worked well.
Then I thought, hey, getElementById
takes exactly 1 argument. Isn't there a syntax sugar for that? So I wrote:
let myArray = ['img1', 'img2', 'img3'].map(document.getElementById);
... but that didn't work. I got "Illegal invocation" on Chrome.
So it's not syntax sugar then. What's behind all these?
JavaScript has a difference between "method call" and "function call". The former will set this
, the latter won't. Syntactically, method call must be of form receiver.method(...args)
. No dot, no method call. So, this:
document.getElementById(id) // method call, `this` is set to `document`
m = document.getElementById; m(id) // function call, `this` is not set
When you do map(document.getElementById)
, document.getElementById
is a function plucked from its object; when map
invokes it, it will invoke it without the receiver, this
will not be set, and things get bad.
There is a way to save it: bind
, which "methodifies" a function by binding a receiver to it: map(document.getElementById.bind(document))
should work.
EDIT: to further illustrate it:
let foo = {
bar: function(context) {
let receiver =
(this == window) ? "window" :
(this == foo) ? "foo" :
"unknown";
console.log(context + ", `this` is `" + receiver + "`");
}
}
function call(fn, param) {
fn(param);
}
foo.bar("In direct call");
let baz = foo.bar; baz("When assigned to a variable");
call(foo.bar, "When passed as a parameter")
let quux = foo.bar.bind(foo); quux("When bound to foo");
You can if you pass in document
to map()
's second this
argument, which will provide the correct context for calling getElementById
. Not sure if this is an improvement over just using the arrow function, though.
let myArray = ['img1', 'img2', 'img3'].map(document.getElementById, document)
console.log(myArray)
<p id="img1"><p>
<p id="img2"><p>
<p id="img3"><p>