men3m
men3m

Reputation: 231

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?

Upvotes: 17

Views: 4905

Answers (5)

user1106925
user1106925

Reputation:

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.

Upvotes: 26

Mike Hamilton
Mike Hamilton

Reputation: 1567

You can use the function getElementsByTagName() which queries the DOM for elements of a certain tag and returns a collection of elements. You can call this function on document or you could be more specific and select an element using document.getElementById() and then look for tags inside of that element.

Either way, once you have a collection of elements, you can then loop over the collection and apply styles accordingly.

//query the document for all <button> elements
var buttons = document.getElementsByTagName('button');

/* -- OR -- */

//query the document for all buttons inside of a specific element
var container = document.getElementById('container');
var buttons = container.getElementsByTagName('button');

//loop over the collection of buttons and set each background to 'red'
for(var i=0; i<buttons.length; i++) {
    buttons[i].style.background = "red";
}

Edit: I realize this is not the JS behind how jQuery's each function works. OP didn't state that he wanted to see that specifically, just a way to accomplish the $.each() functionality with JS (as there are many possibilities). This example is just a trivial approach using a very basic concept.

Upvotes: 5

I wrestled a bear once.
I wrestled a bear once.

Reputation: 23399

Supported by more browsers since it doesn't use forEach.

If using getElementsByTagName, add the function to HTMLCollection.

HTMLCollection.prototype.each = function(callback){
    for(var i=0; i<this.length; i++) callback(this[i]);
};

document.getElementsByTagName('div').each(function(div){
    div.innerHTML = "poo";
});

If using querySelectorAll you can attach the same function to NodeList

NodeList.prototype.each = function(callback){
    for(var i=0; i<this.length; i++) callback(this[i]);
};

document.querySelectorAll('div').each(function(div){
    div.innerHTML = "podo";
});

To appease the chanters (see comments), I'll specify that if this is for a library you're writing, or if it will be used alongside a library that could possibly overwrite it, obviously you would want to consider other methods.

Fiddle: https://jsfiddle.net/er5amk8j/

Upvotes: 3

Bizniztime
Bizniztime

Reputation: 1036

But you are also using $.each() in a dubious way in your example. It is much easier and faster to access elements using this.

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

Most simple replacement would be using simple function closures but it's not pretty.

var $buttons = $('button');

for(var i = 0; i < $buttons.length; i++){
  (function(i){
    $buttons.eq(i).css('background', 'red');
  })(i);
}

Or to set this use .call() to invoke function.

var $buttons = $('button');

for(var i = 0; i < $buttons.length; i++){
  (function(i){
    $(this).css('background', 'red');    
  }).call($buttons[i], i);
}

Upvotes: 2

MyNameIsUser9123
MyNameIsUser9123

Reputation: 49

var buttons = document.querySelectorAll("button");
for(var x in buttons){
  var e = buttons[x];
  e.innerHTML="stuff";
}

as squint pointed out, it should be buttons[x] and var x. sorry about that.

Upvotes: 4

Related Questions