Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 432
men3m Avatar asked Nov 28 '16 21:11

men3m


1 Answers

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");

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";
});

If you put the Array.from call directly in our Q function from above, 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 use .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.


Creating a custom each()

As a side note, if you'd like this to refer to the current element, or have any other specific behavior, here's a basic each implementation that receives the collection and a callback.

function each(a, callback) {
  for (var i = 0; i < a.length; i++) {
    callback.call(a[i], a[i], i, a);
  }
}

Though using this in that way is generally not needed since you already have the element as a parameter.

like image 51
16 revs, 3 users 92%user1106925 Avatar answered Sep 19 '22 14:09

16 revs, 3 users 92%user1106925