skip to Main Content

Does the e.matches() method have to traverse the DOM to evaluate the selector?

I ask because of a situation such as:

if ( e.matches('.class_1, .class_1 *') {
  /* Do something. */
} else if ( e.matches('.class_2, .class_2 *') {
  /* Do something. */
} else if ( e.matches('.class_3, .class_3 *') {
  /* Do something. */
} else {
  /* Do something. */
}

If the DOM is traversed from the element e up the ancestor tree to determine if e.matches() is true/false; then it would repeat the process for each else if when the previous condition is false. So, I wondered if this was not inefficient and I should just loop up the ancestor tree once using e.classList.contains() to find which of the set (class_1, class_2, class_3) is encountered first or reach an ancestor element that indicates that should stop searching. But contains() doesn’t appear to be a quick test either.

let c = e.classList;
while ( e ) {
  if ( c.contains('class_1') ) {
    /* Do something. */
    break;
  } else if ( c.contains('class_2') ) {
    /* Do something. */
    break;
  } else if ( c.contains('class_3') ) {
    /* Do something. */
    break;
  }
  e = e.parentNode;
} 

In this DOM sturcture class_1 is always deeper down than class_2 which is always deeper down than class_3. So, I’d like to determine to which level e belongs first. Which element is the class_1, 2, 3 does not need to be determined, but only to which of them (if any) e is closest to on the way up the tree.

Thank you.

3

Answers


  1. It probably has to traverse the ancestor list so there might be some waste, but traversing it yourself once might not be faster than the browser traversing it 3 times.

    If the site has a noticable performance issue (e.g. if this operation is performed a lot of times), you can measure both methods on different browsers and see what’s faster. But in most cases there wouls be practically no difference anyway and then it’s probably better to go with what makes the code clearer.

    Login or Signup to reply.
  2. You could do the "heavy" work outside any of the loops by analysing the classes that have been assigned to the clicked (or otherwise selected) element:

    document.querySelectorAll("div").forEach(d=>d.onclick=e=>{
      const classes=Object.fromEntries([...e.target.classList].map(cl=>([cl,1])));
      // classes is an object that has class-named properties. These can be easily checked for existence:
      if (classes.class_1) console.log("A class 1 element was clicked");
      else if (classes.class_2) console.log("A class 2 element was clciked");
      else if (classes.class_3) console.log("A class 3 element was clciked");
    })
    .class_1,.class_2,.class_3 {cursor:pointer; background-color:#ddd; margin:6px} 
    <p>Please click on any of the divs below:</p>
    <div class="class_1">class 1 item</div>
    <div class="class_2">class 2 item</div>
    <div class="class_3">class 3 item</div>
    <div class="class_1 class_2">class 1 and 2 item</div>
    <div class="class_2 class_3">class 2 and 3 item</div>
    <div class="class_3 class_1">class 3 and 1 item</div>

    Seeing that your sequence of if statements always filters out the alphabetically lowest "class_…" entry you can simplify the script further to:

    document.querySelectorAll("div[class*=class_]").forEach(d=>d.onclick=e=>{
      const minClass=[...e.target.classList].filter(c=>c.slice(0,6)=="class_").reduce((a,c)=>c<a?c:a)
      if      (minClass=="class_1") console.log("A class 1 element was clicked");
      else if (minClass=="class_2") console.log("A class 2 element was clciked");
      else if (minClass=="class_3") console.log("A class 3 element was clciked");
    })
    .class_1,.class_2,.class_3 {cursor:pointer; background-color:#ddd; margin:6px}
    <p>Please click on any of the divs below:</p>
    <div class="class_1">class 1 item</div>
    <div class="class_2">class 2 item</div>
    <div class="class_3">class 3 item</div>
    <div class="extraClass">this chould not be clickable</div>
    <div class="class_1 abc class_2">class 1 and 2 item</div>
    <div class="class_2 class_3">class 2 and 3 item</div>
    <div class="class_3 class_1">class 3 and 1 item</div>
    Login or Signup to reply.
  3. If I understand the question correctly, it seems that you want the element method closest() using a selector list of your class names:

    document.querySelectorAll(".clickable").forEach((div) => {
      div.addEventListener("click", (ev) => {
        console.log(ev.currentTarget.closest(".class_1, .class_2, .class_3"));
      });
    });
    <div>
      <div class="class_3">
        <div class="class_2">
          <div class="class_1">
            <div class="clickable">Click 1</div>
          </div>
        </div>
      </div>
    
      <div class="class_3">
        <div class="class_2">
          <div class="clickable">Click 2</div>
        </div>
      </div>
    
      <div class="class_3">
        <div class="clickable">Click 3</div>
      </div>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search