What is the most efficient way to traverse up in jQuery
Asked Answered
T

4

6

I'm trying to close a parent container when an internally nested button is clicked. In my UI -- I have many of these parent containers (I'm rendering preview windows of my product catalogue on a product category page).

As you can see from my mark-up below -- the CLOSE button is deeply nested in the DOM. When the user clicks on the CLOSE Button -- I need to hide() the parent Box-1. Keep in mind I may have upto to 100 products being displayed on a page (100 boxes of "Box-1"s) at a time.

My markup looks like this:

<div class="box-1">
  <div class="box-2">
    <div class="box-3">...</div> <!-- end box-3 -->

    <div class="box-4">
      <div class="box-5">...</div> <!-- end box-5 -->
        <a class="btn-close" href="#">CLOSE</a>  <!-- this triggers the close event -->
    </div> <!-- end box-4 -->
  </div> <!-- end box-2 -->

  <div class="box-6">
    <div class="box-7">...</div> <!-- end box-7 -->

    <div class="box-8">
      ...
      <div class="box-9">...</div> <!-- end box-9 -->
    </div> <!-- end box-8 -->
  </div> <!-- end box-6 -->
</div> <!-- end box-1 -->

My question is -- how do I best (and most efficiently) traverse back up the DOM to get hold of the "box-1" and issue the .hide() method ... here is my existing code.

<script>
$productsResultItems.delegate('.btn-close', 'click', function (e) {
    //box-1
    $(this).parents('div.box-1').hide(); // <-- is this the best way?????
    e.preventDefault();
</script>

Initially, I was trying this --

$this.parents().find('.hover-box-large').hide();

which was proving to be very slow in IE7 and IE8.

I found adding more detail to the selector improved performance nearly 100-fold for IE7, but only 4-fold faster in IE8 :( IE8 still requires about 200ms to close the parent container. Where now all other browsers (Chrome, Safari, Firefox and IE7) close the container in less than 20ms.

$this.parents('div.hover-box-large').hide();

But is there a selector method that is even better? Any particular reason IE8 is soooo bad at this type of upward traversal??

Theomania answered 6/6, 2011 at 14:42 Comment(0)
M
7

The best method to use is closest, which finds the nearest ancestor element that matches a selector:

$this.closest('div.box-1').hide();
Mcilroy answered 6/6, 2011 at 14:45 Comment(1)
Folks -- all three answers are correct. I'm granted the correct answer to lonesomeday as his/her came in first. Thank you all for your help. Using 'closest' was a great suggestion. It cut IE8 rendering time by half!Theomania
M
3

Actually .closest() should be quicker than .parents().

In the jQuery Docs on .closest(), you can find:

.closest()

  • Begins with the current element
  • Travels up the DOM tree until it finds a match for the supplied selector
  • The returned jQuery object contains zero or one element

.parents()

  • Begins with the parent element
  • Travels up the DOM tree to the document's root element, adding each ancestor element to a temporary collection; it then filters that collection based on a selector if one is supplied
  • The returned jQuery object contains zero, one, or multiple elements

So in your case, .closest() would be the most suitable one, as you need to find one element, the closest ancestor that matches your selector. parents() would filter through all the possible ancestor elements, even if it already found the one you need.

Milker answered 6/6, 2011 at 14:47 Comment(0)
L
1

The only difference between parents() and closest() is that closest() stops once it has found a match therefore always returning 0 or 1 elements. Parents() will match everything up the DOM.

$(this).closest('.box-1').hide();
Lemieux answered 6/6, 2011 at 14:45 Comment(1)
Thanks John -- great concise answer -- much appreciated. It cut the IE8 rendering time in half!Theomania
I
0

Not so fast! closest() may be the best but not always! Here's how you can find out for yourself. Use the Firebug time() and timeEnd() functions to actually log your calls. Then choose the one that is right for the situation.

// 59ms
console.time("Parent x 3");
$container = $element.parent().parent().parent();
console.timeEnd("Parent x 3");

// 3ms              
console.time("Closest parent");
$container = $element.closest('.some-class').parent();
console.timeEnd("Closest parent");

// 2ms              
console.time("Parents");
$container = $element.parents('.other-class').eq(1);
console.timeEnd("Parents"); 
Ingot answered 25/7, 2011 at 22:8 Comment(1)
+1 for testing to specific instances; your own DOM can affect things a lot. console.time() and console.timeEnd() are in Chrome too. Try doing these benchmarks in different orders as well: when I test .closest() first, five chained .parent() calls are just as fast, but if I test the .parent()s first and then .closest(), the closest() test takes half the time.Towel

© 2022 - 2024 — McMap. All rights reserved.