2

Sorry if this is a dumb Q, I'm super new... Why do we need to define the var first and then run the loop? The code seems to work just fine the other way. Is there a benefit to the first way that I'm missing?

eg.

const buttons = document.querySelectorAll('.button');
    buttons.forEach(button => button.addEventListener('event', function));

instead of just:

document.querySelectorAll('.button').forEach(button => {
    button.addEventListener('event', function)
});

Thanks for your help!

MarwaMite
  • 23
  • 4
  • 3
    If you're going to do 20 different things with the list of elements, it saves all those repeated lookups to stash a reference to the list in a variable. – Pointy Jan 19 '20 at 14:59
  • If you define a separate variable to store the references to all DOM elements with the class `.button`, you could do some conditional check to ensure the length of the array is more than one for instance before applying the event listener. – junwen-k Jan 19 '20 at 15:01

4 Answers4

1

Why do we need to define the var first and then run the loop?

You don't.

You might choose to, if you're going to do more than one thing with that set of elements (because unlike getElementsByTagName, querySelectorAll doesn't reuse the collection; every call to it re-queries the DOM). But if you're doing just one thing, there's no reason why you can't do it as shown in your second example.


Side note: The NodeList from querySelectorAll only has forEach on relatively-modern browsers. But you can polyfill it trivially for older ones; this answer shows how.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

The only advantage is that the variable buttons here, is reusable. You don't have to invoke querySelectorAll() every time you want to work with the list of the elements with class button. Else, it's pretty much the same.

DumbCoder7
  • 255
  • 1
  • 8
0
const buttons = document.querySelectorAll('.button');

These are some cases to do that (in my experience):

  • You can reuse the buttons variable, such as: sending it as a parameter to another function.

  • It makes your code easier to read:

Try to compare this

document.querySelectorAll('.button').forEach(button => button.addEventListener('event', function () {}));

with this:

const buttons = document.querySelectorAll('.button');
buttons.forEach(button => button.addEventListener('event', function () {}));
  • It's useful for debugging:

An example about typo problem: You're calling buttons.foreach instead of buttons.forEach. The logger complains an error but you don't know why, then you can check it using console.log

const buttons = document.querySelectorAll('.button');
console.log(buttons); // check for existing
console.dir(buttons); // check for containing "foreach" function in the prototype
Tân
  • 1
  • 15
  • 56
  • 102
0

You don't have to do anything. Why would you want to do anything at all then?

If we're talking conceptual difference, and use a richer example, one directly accesses a low-level construct (document.querySelectorAll) to perform some effort in-place (cohesion). If we talk domain, then, the alternative is to refer to items in their higher-level (application) construct, indirecting and encapsulating some core logical concern into a detail of the system instead of it's purpose (adhesion).

We often solve for outcome first, then determine purpose, which is backwards (IMO) except in fast prototyping early on, when little forethought is possible or practical. Basing reference naming constructs on software issues ("I need to get the buttons") makes the argument harder to see, in my opinion, of the why we make references except "don't do something (whatever) twice in the same place".

For instance, if our current view is products of some sort (for some reason) located in a DOM element list of products, we could abstract it into reference handles that represent the core domain constructs, making the how a concern of some other (lower, further in) part of the code.

If you think about it, the fact of the buttons existing is less interesting than what they accomplish (and why we would want to refer to them in the first place). High-level references then deal with the core problem domain, abstracting and indirecting the software domain issue of locating and performing some action against those things.

Using rich reference naming constructs helps to simplify your codebase's outcomes while making the purpose and intention of those logical constructs more compelling and easier to infer.

const productNotFound = id => {
  throw 'Product '+id+' not found.'
}

const products = ids => ids === undefined ? [...productList] : ids.map(product)
const product = id => {
    const find = (found, item) => {
       return found || (item.dataset && item.dataset.id == id && item) || null
    }
    return [...productList].reduce(find, null) || productNotFound(id)
}

const queryProducts = () => document.querySelectorAll('#products > li')

const resetProduct = id => {
  let item = product(id)

  item.classList.remove(...item.classList)

  return item 
}

const addProduct = id => {
  let item = document.createElement('li')
  
  item.dataset.id = id
  item.textContent = 'Product '+id

  document.getElementById('products').appendChild(item)
}
const addProducts = ids => {
  ids.map(addProduct)
  productList = queryProducts() // Update core reference after addProduct
}
const selectProduct = id => resetProduct(id).classList.add('selected')
const archiveProduct = id => resetProduct(id).classList.add('archived')
const removeProduct = id => resetProduct(id).classList.add('deleted')

let productList = queryProducts()

try {
  addProducts([1, 2, 3, 4, 5])
  
  console.log(products().map(item => item.textContent))
  console.log(products([1, 2]).map(item => item.textContent))

  selectProduct(2)
  selectProduct(3)
  removeProduct(3)
  archiveProduct(1)
  removeProduct(4)

  addProducts([10, 11, 12])

  selectProduct(12)

  setTimeout(() => [1,3,5].map(archiveProduct), 3000)

  removeProduct(500)
} catch (error) {
  console.log('Error!', error)
}
.selected {
    font-weight: bold;
    color: blue;
}
.archived {
    font-weight: bold;
    color: #aaa;
}
.archived::after {
    content: ' (Archived)'
}
.deleted {
    text-decoration: line-through;
    color: red;
}
<ul id="products"></ul>
Jared Farrish
  • 48,585
  • 17
  • 95
  • 104