2

I'm reading about querySelector method, the article suggests that one for example shall use Array.prototype.map.call() instead of map(). I have no problem with that, however I don't really see why Array.prototype.map.call() should be chosen over Array.map() What makes one better than the other?

let nodes = document.querySelectorAll('div.menu-item')

Array.prototype.map.call(nodes, one => one.innerHTML)
//vs
Array.map(nodes, one => one.innerHTML)
Xun Yang
  • 4,209
  • 8
  • 39
  • 68
  • `Array.from(document.querySelectorAll('div.menu-item')).map(one => one.innerHTML)` would be better seeing as your in an ES2015 (Calling it ES6 is a tough habit to get out of) environment. – ste2425 Aug 08 '17 at 09:33
  • @ste2425: Better yet, use the mapping feature of `Array.from`: `Array.from(document.querySelectorAll('div.menu-item'), o‌​ne => one.innerHTML)` – T.J. Crowder Aug 08 '17 at 09:35
  • @ste2425 you'd use `Array.from(document.querySelectorAll('div.menu-item'), o‌​ne => one.innerHTML)` - second argument to Array.from is a map function *snap* – Jaromanda X Aug 08 '17 at 09:35
  • 1
    @T.J.Crowder Cracking, was not aware it has that second feature. – ste2425 Aug 08 '17 at 09:36
  • I'm actually not asking about `map()` function per se but thanks for mentioning `from()`! – Xun Yang Aug 08 '17 at 09:43
  • should change the line `Array.map(nodes, one => one.innerHTML)` to `nodes.map(one => one.innerHTML)` – raksa Aug 08 '17 at 09:47

2 Answers2

3

First, there is no Array.map. The use case for the former variant is the following: You won't be able to invoke map for example via document.querySelectorAll('div')[0].map. That is to the fact, that document.querySelectorAll returns a NodeList and not an array and lacks the map method.

What makes one better than the other?

The question really is not which variant is better. It is about not being able to call map on the NodeList.

Most functions within the array prototype are able to handle array-like objects as well (that is, objects whose keys are representations of numeric indices) when passed as thisArg to call(or apply). Hence,Array.prototype.map.call(array-like, mapFunction)` can map the NodeList just like an array.

There's two alternatives, however:

  1. Use Array.from: Array.from(nodes, n => n.innerHTML) introduced in ES6 and not supported in Internet Explorer. However, when that does not matter, I would prefer this.
  2. Use Array.prototype.slice.call(nodes).map(n => n.innerHTML), which works similar (see how does Array.prototype.slice.call() work?) or stick with Array.prototype.map.call.

According to your overall code, however, the usage of (1) seems perfectly fine and I, personaly, would prefer this.

SVSchmidt
  • 6,269
  • 2
  • 26
  • 37
  • As I said in my answer, if you're using `Array.from`, immediately using `map` on the result is pointless and inefficient. Just use the callback argument for `Array.from`. – T.J. Crowder Aug 08 '17 at 10:03
2

What makes one better than the other?

There is no Array.map in the standard JavaScript library. So if you use it, you're relying on an unspecified feature that may or may not be present in all of your target environments. (Firefox's SpiderMonkey JavaScript engine provides it, for instance; Chrome's V8 engine does not, as of this writing.) So that would be a fairly substantial reason to prefer Array.prototype.map.call — of those two choices. :-)

But, as ste2425 points out, given your use of let and arrow functions, you appear to be coding for an ES2015+ environment. In that case (or if you add a polyfill), you can use the mapping features of Array.from:

let innerHTMLArray = Array.from(nodes, o‌​ne => one.innerHTML);

Array.from creates an array from any array-like object or iterable object. The NodeList from querySelectorAll is both. :-) And it optionally lets you map the values as it's doing that.

Example:

if (!Array.from) {
  console.error("Your browser doesn't support Array.from yet.");
} else {
  const htmlArray = Array.from(document.querySelectorAll("div.menu-item"), e => e.innerHTML);
  console.log(htmlArray);
}
<div>I'm not included in the result because I don't have the class</div>
<div class="menu-item">Menu item #1</div>
<div class="menu-item">Menu item #2</div>
<div class="menu-item">Menu item #3</div>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Ahhh now I see, I'm using react so probably Babel added all those extra functions. With a blank page in chrome console `Array.map` is undefined. So is `Array.map()` added in es2015 standard? – Xun Yang Aug 08 '17 at 09:48
  • @XunYang: No. As I said, it's not part of the current standard JavaScript library -- no in ES2015, ES2016, or ES2017, nor do I see any [proposal](https://github.com/tc39/proposals) for it. – T.J. Crowder Aug 08 '17 at 10:02
  • @XunYang: Perhaps you're just using Firefox? :-) – T.J. Crowder Aug 08 '17 at 10:15
  • No, I'm using chrome, and they are clearly added only in my react project... And can't find much documentation about these functions in MDN either. Wondering why. – Xun Yang Aug 08 '17 at 11:05
  • @XunYang: I don't know what you mean by "these functions." [`Array.prototype.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and [`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) both have MDN pages, because they're standard functions. `Array.map` doesn't because it **isn't**. So sure, maybe you have some Babel setting adding it; it's not part of the bare-bones stuff done (for instance, in a [JSX snippet](https://meta.stackoverflow.com/questions/338537/) here on SO). – T.J. Crowder Aug 08 '17 at 11:22
  • Almost every function attached to `Array.prototype` is available in `Array` directly. As you said Firefox provides it, but they have little documentation about their existence or why they made them available. I suspect react is attaching them to make it compatible with Firefox, or I just don't know :( – Xun Yang Aug 08 '17 at 11:27
  • @XunYang: They're all documented [on MDN here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype) and in [the specification here](http://www.ecma-international.org/ecma-262/8.0/index.html#sec-properties-of-the-array-prototype-object). – T.J. Crowder Aug 08 '17 at 11:29
  • @T.J.Crowder The documentation is only about the functions attached to `Array.prototype` right? Not directly to `Array` – Xun Yang Aug 08 '17 at 11:31
  • @XunYang: Yes. That's what you asked about above. But if you look, you'll find sections both on MDN and in the spec talking about functions directly on `Array`, as well (such as `Array.from`, linked above). – T.J. Crowder Aug 08 '17 at 11:33
  • I trust you, it's somewhere – Xun Yang Aug 08 '17 at 11:40