4

Consider the following code:

var els = document.querySelectorAll('.myClassName');

Array.prototype.forEach.call(els, function(el) {
  console.log(el.id);
});

The var els contains a nodelist, which is not an array, and forEach applies on arrays only right? Is the above code actually a hack?

poashoas
  • 1,790
  • 2
  • 21
  • 44
  • From which definition you say nodeList is not an array? it is an array. – Pawan Nogariya May 30 '16 at 20:54
  • 1
    @PawanNogariya: Probably [the specification](https://www.w3.org/TR/domcore/#interface-parentnode): `[NewObject] NodeList querySelectorAll(DOMString selectors);` – T.J. Crowder May 30 '16 at 20:55
  • 1
    The forEach method expects the variable to be a type array and not the type NodeList (correct me if I am wrong). When I look at the Nodelist in my developer tool from Chrome I can see no forEach available. – poashoas May 30 '16 at 20:59

1 Answers1

5

...and forEach applies on arrays only right?

Nope. Array.prototype.forEach is intentionally generic, it can be applied to any object that is array-like. From the spec:

NOTE2: The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

The spec clearly lays out what properties and/or methods will be used during the processing of forEach; as long as the object referenced via this during the call has those, forEach can be used on that object. That's why using forEach.call like that works: The call method on function objects (forEach is a function object) calls the function using the first argument you give call as this during the call, and passing along the following arguments as the arguments to the original function. So Array.prototype.forEach.call(x, y) calls forEach with this set to x and with the first argument set to y. forEach doesn't care about the type of this, just that it has the relevant properties and methods as described in the specification's algorithm for it.

Most of the Array.prototype methods are like that, and indeed many others on the other standard prototypes.


Side note: The NodeList returned by querySelectorAll recently became iterable on modern browsers, whcih means: 1.  It works with ES2015+'s for-of, and 2. It has forEach natively now. (On modern browsers.)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • So when a variable is really an array I can call forEach directly, and if not I use the other approach? – poashoas May 30 '16 at 21:02
  • @poashoas: You can also put a property on it that refers to the `forEach` function, and call the method directly. It's only that arrays inherit the property with the function from `Array.prototype` while other objects don't. – Bergi May 30 '16 at 21:03
  • 1
    @poashoas: Mostly, yes. More precisely: So long as you have a reference to an object that has a reference to the `Array.prototype.forEach` function as a property (directly, or because -- like a normal array -- it inherits that from its prototype), you can use `forEach` on it directly. If it doesn't, but it is *array-like*, you can use the `call` approach on it. – T.J. Crowder May 30 '16 at 21:04
  • @Bergi I am trying to learn :-D How do you put a property on a NodeList. Something like NodeList.prototype.foreach = function() { for(var i = 0; i < this.length; i++ ){ ... } } ? – poashoas May 30 '16 at 21:06
  • @poashoas: Yes, you could do that, but extending default prototypes can be problematic. If you *do* do it, don't do it with assignment like that because it creates an *enumerable* property. Use `defineProperty`: `Object.defineProperty(NodeList.prototype, "forEach", {value: Array.prototype.forEach});` But again, beware of extending native prototypes that way, and always test on your target JavaScript engines and with the libraries you want to use, in order to: A) Make sure it works (host-provided objects don't have to support it), and B) Check for conflicts. – T.J. Crowder May 30 '16 at 21:08
  • @poashoas: I answered before you edited in your implementation. See above, you can *reuse* `Array.prototype.forEach`, you don't have to reimplement it. – T.J. Crowder May 30 '16 at 21:11
  • 1
    @T.J.Crowder that was really helpfull, I might try some stuff with this – poashoas May 30 '16 at 21:16