men3m men3m - 11 days ago 6
Javascript Question

What's the substitute for jQuery's each() function in JavaScript?

In jQuery we have a function each like this

$('button').each(function(i) {
$('button').eq(i).css('background', 'red');
});


How can we replace this code with plain JavaScript?

Answer

DOM selection with querySelectorAll()

Browsers have several ways to select elements from the DOM, but perhaps the most flexible and convenient is querySelectorAll. It lets you use CSS style selectors to grab all matching elements from a given root.

In your case, it would look like this:

document.querySelectorAll("button");

Shortening querySelectorAll()

As nice as that is, it is a little verbose, so it's not uncommon to create a wrapping function that shortens it. That's what we'll do here, giving it the name Q.

function Q(root, selector) {
  if (typeof root === "string") {
    selector = root
    root = document
  }
  return root.querySelectorAll(selector)
}

The first argument is the context from which you're doing the selection, and the second is the selector. If you only pass a string, it'll use document as the context.

So now your DOM selection would be this, which we'll use hereafter:

Q("button");

Borrowing Array.prototype.forEach

A pretty common way to do a functional looping construct is to borrow the forEach method of Array.prototype and call it on the collection of elements by using the function's .call() method like this:

Array.prototype.forEach.call(Q("buttons"), function(el) {
  el.style.background = "red";
});

Or in the most modern browsers, we can use arrow functions to shorten it a little:

Array.prototype.forEach.call(Q("buttons"), el => el.style.background = "red");

Binding and caching the borrowed .forEach()

The .forEach() borrow can be shortened if early in your application, you use the Function prototype's bind() method to bind the .forEach() method to the this value of .call().

const each = Function.call.bind(Array.prototype.forEach);

That way you can just call it like a function that receives the element collection as the first argument.

each(Q("button"), function(el) {
  el.style.background = "red";
});

Or again using an arrow function in some of the newest browsers:

each(Q("button"), el => el.style.background = "red");

Partially replicating jQuery's each()

If you need this to refer to the current list element, and to be able to break the loop like with jQuery.each, then we can replicate this functionality like so:

function each(a, callback) {
  for (var i = 0; i < a.length; i++) {
    if (callback.call(a[i], a[i], i, a) === false) {
      break; // Break the loop when the callback returns `false`
    }
  }
}

This passes a reference to the array-like object itself and the current index to the callback function like jQuery does. Note that the order of the arguments isn't like jQuery, but has been retained to match that of the callback above. You can reorder those as you see fit.

This also checks the return value of the callback to see if it equals false, and if so, it halts the loop.


Using Array.from()

The Array.from method was also introduced to easily convert array-like objects into actual arrays. This would let you use .forEach() directly, and can be patched into legacy browsers with a simple polyfill (see the docs link).

Array.from(Q("button")).forEach(function(el) {
  el.style.background = "red";
});

And if you put the Array.from call directly in our Q method from above, then you'll be able to call .forEach() directly.

Q("button").forEach(function(el) {
  el.style.background = "red";
});

Using a for-of loop

In the latest browsers, you can use a for-of loop instead, making everything very short and clean:

for (const el of Q("button")) {
  el.style.background = "red";
}

This way there's no need to convert to an Array or borrow .forEach.


Transpiling modern code

For the examples above that require the most modern browsers, there are transpilers available (for example Babel) that will translate the latest standards into code that will work in older browsers.