Search code examples
javascriptjquerycss-selectorsfluent-interface

In jQuery, can I use functions instead of CSS selectors?


Rather than use $('.list').find('li:not(.done):last'), which returns all LI that is not .done and is the last of every .list, how can I use functions instead?

For instance, $('.list').find('li').not('.done').last() would return just one item, because it searches all .list for LI that aren't .done and then just returns the last one.

There are a few reasons I want to do this. Primarily, typically using functions are faster than CSS selectors (in some cases, especially when you split them up into multiple functions as jQuery doesn't have to manually split up the selectors, and oftentimes jQuery functions are just mapped directly to existing CSS selectors anyway). But anyway, curiosity is my main reason at the moment, and performance is secondary.

Sample code (not entirely representative of the actual code, but the structure is similar):

<ul class="list">
  <li>One</li>
  <li class="done">Two</li>
</ul>

<ul class="list">
  <li class="done">Three</li>
  <li>Four</li>
</ul>

I want to return the nodes for:

One and Four.


Solution

  • I'm not coming up with a way to do it that doesn't require looping, e.g.:

    $(".list").map(function() {
      var items = $(this).find("li").not(".done");
      return items[items.length - 1];
    });
    

    // Old way
    $(".list").find("li:not(.done):last").css("color", "red");
    
    // Just to show it's wrong (as the question says)
    $(".list").find("li").not(".done").last().css("background-color", "yellow");
    
    // Alternative
    $(".list").map(function() {
      var items = $(this).find("li").not(".done");
      return items[items.length - 1];
    }).css("border", "1px solid black");
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <ul class="list">
      <li>One</li>
      <li>Two</li>
      <li class="done">Three (done)</li>
    </ul>
    <ul class="list">
      <li>One</li>
      <li>Two</li>
      <li class="done">Three (done)</li>
    </ul>


    The reason you might think using selectors is slower is that selectors like :last are jQuery extensions, not true CSS selectors, meaning that Sizzle (jQuery's selector engine) has to process them in JavaScript, rather than offloading them to the (much faster) selector engine built into the browser. If you restrict yourself to CSS selectors the browser implements, typically the selector will win (modulo other factors, and YMMV). I can't account for your seeming to see the DOM being changed by :not(.done) (which is a valid CSS selector), unless Sizzle does something It Really Shouldn't to process that (since Sizzle supports complex selectors within :not whereas CSS doesn't).