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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With