Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery: subtle difference between .has() and :has()

Tags:

jquery

sizzle

When used with the child selector >, the two variants of jQuery's "has" behave differently.

Take this HTML:

<div>
  <span>Text</span>
</div>

Now:

$("div:has(>span)");

would return it, while:

$("div").has(">span");

would not. Is it a bug or a feature? Compare here: http://jsfiddle.net/aC9dP/


EDIT: This may be a bug or at least undocumented inconsistent behavior.

Anyway, I think it would be beneficial to have the child selector consistently work as an unary operator. It enables you to do something that otherwise would require a custom filter function — it lets you directly select elements that have certain children:

$("ul:has(>li.active)").show();     // works
$("ul").has(">li.active)").show();  // doesn't work, but IMHO it should

as opposed to:

$("ul").filter(function () {
  return $(this).children("li.active").length > 0;
}).show();

I've opened a jQuery ticket (7205) for this.

like image 432
Tomalak Avatar asked Oct 15 '10 18:10

Tomalak


1 Answers

This happens because the sizzle selector is looking at all Div's that have span children in the :has example. But in the .has example, it's passing all DIV's to the .has(), which then looks for something that shouldn't be a stand-alone selection. ("Has children of nothing").

Basically, :has() is part of the selection, but .has() gets passed those divs and then re-selects from them.

Ideally, you don't use selectors like this. The > being in the selector is probably a bug, as it's semantically awkward. Note: the child operator isn't meant to be stand-alone.

Sizzle vs target.sizzle:

I'm always talking about v1.4.2 of jquery development release.

.has (line 3748 of jQuery)

Description: Reduce the set of matched elements to those that have a descendant that matches the selector or DOM element.

Code:

    var targets = jQuery( target );
    return this.filter(function() {
        for ( var i = 0, l = targets.length; i < l; i++ ) {
            if ( jQuery.contains( this, targets[i] ) ) { //Calls line 3642
                return true;
            }
        }
    });

Line 3642 relates to a 2008 plugin compareDocumentPosition, but the important bit here is that we're now basically just running two jquery queries here, where the first one selects $("DIV") and the next one selects $(">span") (which returns null), then we check for children.

:has (line 3129 of jQuery)

Description: Selects elements which contain at least one element that matches the specified selector.

Code:

return !!Sizzle( match[3], elem ).length;

They are two differnt tools, the :has uses sizzle 100%, and .has uses targets passed to it.

Note: if you think this is a bug, go fill out the bug ticket.

like image 105
Incognito Avatar answered Oct 14 '22 06:10

Incognito