How to properly use and understand the call() method while using forEach() method

56 views Asked by At

Four months into javascript, and I ran into the following code that I can't put in terms of the techniques that I understand. I was hoping that someone could refactor this code not in order to satisfy what its purpose is, but to put it in terms that I would understand. Perhaps just explaining how the forEach() works with call(). I understand them separately but not combined.

I have tried to rewrite it without the forEach and call but that defeats my purpose. I can implement this functionality without these two, but it sure makes the code succinct with them.

let parentElement = document.getElementById("node1");
let array = [];
parentElement.addEventListener("click", (e) => {
  var elems = document.querySelectorAll(".active"); // make them all active 
  array.forEach.call(elems, function(el) {
    el.classList.remove("active"); // remove active
  });
  e.target.className = "active"; // set clicked element to active
});
.active,
.btn:hover {
  background-color: #666;
  color: white;
}
<div id="node1">
  <button class="btn">1</button>
  <button class="btn">2</button>
</div>

2

There are 2 answers

0
Brad On

This code pattern is a bit dated. I'll explain how it works, and then show you a modern method.

var elems = document.querySelectorAll(".active"); // make them all active 

The value of elems at this point is a NodeList. It is not an Array.

Back in the day, you couldn't use .forEach() on a NodeList. Therefore, you needed to borrow that method from Array. (It was added later, but for this example let's assume it doesn't exist yet.)

array.forEach.call(elems, function (el) {…});

This line allows you to use Array.forEach() over the elems NodeList. You're executing/calling the array.forEach function, with elems as the "this" object for context.

The Modern Way

These days, you can use for … of, which allows you to iterate over iterables like NodeList, Arrays, and many others.

for (const el of document.querySelectorAll('.active')) {
  el.classList.remove('active');
}

I think you'll find this syntax easier to read and understand.

(By the way, have you considered that radio button inputs might fit better for your use in this case? You can style them to look like buttons.)

0
mplungjan On

We no longer need Array.call.

Also var elems = document.querySelectorAll(".active"); // make them all active does not make all active, but just selects the active ones

All modern browsers can do .forEach on the static (not live) NodeList returned from querySelectorAll (you need to spread to use map and filter line this: [...elems].map(el => something(el)))

Here is the more concise modern way to do want you want - note the classList.toggle with the boolean to force the class when true

document.getElementById("node1").addEventListener("click", (e) => {
  const tgt = e.target;
  document.querySelectorAll(".btn")
    .forEach(el => el.classList.toggle("active",el === tgt)); 
});
.active,
.btn:hover {
  background-color: #666;
  color: white;
}
<div id="node1">
  <button class="btn">1</button>
  <button class="btn">2</button>
</div>