具有兄弟方法的伪选择器

发布于 2024-12-10 11:10:28 字数 1842 浏览 0 评论 0原文

早些时候,我回答了这个问题,基本上是关于删除表格行。这个问题是由于对该问题的评论而产生的。给定以下 HTML:

<div><a href="#" class="removelink">remove</a></div>
<table>
    <tr>
        <td>Row 1</td> 
    </tr>
</table>

和以下 jQuery:

$('.removelink').click(function(){
    $(this).parent().siblings('table tr:last').remove();
});

我希望什么也不会发生,因为 siblings方法应该选择当前匹配元素的同级元素,可以选择通过选择器进行过滤。来自 jQuery 文档:

该方法可以选择接受相同类型的选择器表达式 我们可以将其传递给 $() 函数。如果提供了选择器,则 元素将通过测试是否匹配来过滤。

基于此,我将上面的代码读为“获取当前元素(div)的同级元素,它们是table中的最后一个tr >”。显然没有与该描述匹配的元素 - table 中有一个 tr,但它不是 div 的同级元素。因此,我不希望返回任何元素。然而,它实际上返回整个表,就好像它完全忽略了选择器的 tr:last 部分一样。

让我更加困惑的是,如果删除 :last 伪选择器,它会按预期工作(不返回任何元素)。

为什么上面的代码删除了整个表?我只是愚蠢并且错过了一些明显的事情吗?您可以在此处查看上面的代码。

编辑 - 这是一个简化版本。给定以下 HTML:

<div id="d1"></div>
<div>
    <span></span>
</div>

为什么以下 jQuery 返回第二个 div

$("#d1").siblings("div span:last");

我希望它不会返回任何内容,因为没有 span的同级>#d1这是这个简化示例的小提琴

更新

根据@muistooshort的精彩调查,我创建了一个jQuery错误票< /a> 来跟踪这个问题。

Earlier, I answered this question, which was basically about removing a table row. This question came about as the result of the comments on that question. Given the following HTML:

<div><a href="#" class="removelink">remove</a></div>
<table>
    <tr>
        <td>Row 1</td> 
    </tr>
</table>

And the following jQuery:

$('.removelink').click(function(){
    $(this).parent().siblings('table tr:last').remove();
});

I would expect nothing to happen, because the siblings method should select the siblings of the currently matched element, optionally filtered by a selector. From the jQuery docs:

The method optionally accepts a selector expression of the same type
that we can pass to the $() function. If the selector is supplied, the
elements will be filtered by testing whether they match it.

Based on that, I read the above code as "get the siblings of the current element (the div) which are the last tr within a table". Obviously there are no elements that match that description - there is a tr within a table, but it's not a sibling of the div. So, I wouldn't expect any elements to be returned. However, it actually returns the entire table, as if it ignores the tr:last part of the selector entirely.

What confused me further was that if you remove the :last pseudo-selector, it works as expected (returning no elements).

Why is the entire table removed by the above code? Am I just being stupid and missing something obvious? You can see the above code in action here.

Edit - Here's a simplified version. Given the following HTML:

<div id="d1"></div>
<div>
    <span></span>
</div>

Why does the following jQuery return the second div:

$("#d1").siblings("div span:last");

I would expect it to return nothing, as there is not a span which is a sibling of #d1. Here's a fiddle for this simplified example.

Update

Following the brilliant investigation from @muistooshort, I have created a jQuery bug ticket to track this issue.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

青巷忧颜 2024-12-17 11:10:28

请允许我稍微扩展一下我的评论。所有这些都基于您的第二个简化示例和 jQuery 1.6.4。这可能有点啰嗦,但我们需要浏览一下 jQuery 代码来了解它在做什么。


我们确实有可用的 jQuery 源代码,所以让我们去看看吧
穿过它,看看里面有什么奇迹。

siblings 的内部结构看起来像这样:

siblings: function( elem ) {
    return jQuery.sibling( elem.parentNode.firstChild, elem );
}

包裹在这个:

// `name` is "siblings", `fn` is the function above.
jQuery.fn[ name ] = function( until, selector ) {
    var ret = jQuery.map( this, fn, until )

    //...

    if ( selector && typeof selector === "string" ) {
        ret = jQuery.filter( selector, ret );
    }

    //...
};

然后 jQuery.sibling 是这样的:

sibling: function( n, elem ) {
    var r = [];

    for ( ; n; n = n.nextSibling ) {
        if ( n.nodeType === 1 && n !== elem ) {
            r.push( n );
        }
    }

    return r;
}

所以我们在 DOM 中向上一步,转到父级的第一个步骤孩子,
并继续横向获取所有父母的孩子(除了
我们开始的节点!)作为 DOM 元素的数组。

这样我们就可以得到 ret 中的所有同级 DOM 元素了
现在来看看过滤:

ret = jQuery.filter( selector, ret );

那么 filter 到底是什么? filter 就是这样:

filter: function( expr, elems, not ) {
    //...
    return elems.length === 1 ?
        jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
        jQuery.find.matches(expr, elems);
}

在您的情况下,elems 将只有一个元素(如 #d1
有一个兄弟)所以我们开始使用 jQuery.find.matchesSelector
实际上是 Sizzle.matchesSelector

var html = document.documentElement,
    matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
//...
Sizzle.matchesSelector = function( node, expr ) {
    // Make sure that attribute selectors are quoted
    expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");

    if ( !Sizzle.isXML( node ) ) {
        try {
            if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
                var ret = matches.call( node, expr );

                // IE 9's matchesSelector returns false on disconnected nodes
                if ( ret || !disconnectedMatch ||
                        // As well, disconnected nodes are said to be in a document
                        // fragment in IE 9, so check for that
                        node.document && node.document.nodeType !== 11 ) {
                    return ret;
                }
            }
        } catch(e) {}
    }

    return Sizzle(expr, null, null, [node]).length > 0;
};

一些实验表明 Gecko 和 WebKit 都没有
matchesSelector 的版本可以处理 div span:first 所以我们结束
在最后的 Sizzle() 调用中;请注意,Gecko 和 WebKit
matchesSelector 变体可以处理 div span 和您的
jsfiddles 在 div span 情况下按预期工作。

Sizzle(expr, null, null, [node]) 是做什么的?为什么它返回一个数组
当然,您的

中包含 。我们会有
expr 中的 this:

'div span:last'

以及 node 中的 this:

<div id="d2">
    <span id="s1"></span>
</div>

因此 位于 node 中> 与选择器完美匹配
expr 中,并且 Sizzle() 调用返回一个包含
并且由于该数组具有非零长度,因此 matchesSelector
call 返回 true,一切都化为一堆废话。

问题是在这种情况下 jQuery 无法与 Sizzle 正确交互。恭喜你,你是一只弹跳小虫子的骄傲的父亲。

这是一个(大量)jsfiddle,其中包含 jQuery 的内联版本,并有几个 console.log 调用来支持我上面所说的内容:

http://jsfiddle.net/ambigously/TxGXv/

需要注意的一些事项:

  1. 您将得到 <使用 div spandiv span:nth-child(1) 得到合理的结果;它们都使用本机 Gecko 和 WebKit 选择器引擎。
  2. 使用 div span:firstdiv span:last 甚至 div span:eq(0 时,您都会得到相同的损坏结果);;所有这三个都经过 Sizzle。
  3. 正在使用的 Sizzle() 调用的四个参数版本未记录(请参阅 公共 API),所以我们不知道 jQuery 或 Sizzle 是否有问题。

Allow me to expand on my comment a little bit. All of this is based on your second simplified example and jQuery 1.6.4. This is a little long winded perhaps but we need to walk through the jQuery code to find out what it is doing.


We do have the jQuery source available so let us go a wandering
through it and see what wonders there are to behold therein.

The guts of siblings looks like this:

siblings: function( elem ) {
    return jQuery.sibling( elem.parentNode.firstChild, elem );
}

wrapped up in this:

// `name` is "siblings", `fn` is the function above.
jQuery.fn[ name ] = function( until, selector ) {
    var ret = jQuery.map( this, fn, until )

    //...

    if ( selector && typeof selector === "string" ) {
        ret = jQuery.filter( selector, ret );
    }

    //...
};

And then jQuery.sibling is this:

sibling: function( n, elem ) {
    var r = [];

    for ( ; n; n = n.nextSibling ) {
        if ( n.nodeType === 1 && n !== elem ) {
            r.push( n );
        }
    }

    return r;
}

So we go up one step in the DOM, go to the parent's first child,
and continue sideways to get all of the parent's children (except
the node we started at!) as an array of DOM elements.

That leaves us with all of our sibling DOM elements in ret and
now to look at the filtering:

ret = jQuery.filter( selector, ret );

So what is filter all about? filter is all about this:

filter: function( expr, elems, not ) {
    //...
    return elems.length === 1 ?
        jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
        jQuery.find.matches(expr, elems);
}

In your case, elems will have have exactly one element (as #d1
has one sibling) so we're off to jQuery.find.matchesSelector which
is actually Sizzle.matchesSelector:

var html = document.documentElement,
    matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
//...
Sizzle.matchesSelector = function( node, expr ) {
    // Make sure that attribute selectors are quoted
    expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");

    if ( !Sizzle.isXML( node ) ) {
        try {
            if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
                var ret = matches.call( node, expr );

                // IE 9's matchesSelector returns false on disconnected nodes
                if ( ret || !disconnectedMatch ||
                        // As well, disconnected nodes are said to be in a document
                        // fragment in IE 9, so check for that
                        node.document && node.document.nodeType !== 11 ) {
                    return ret;
                }
            }
        } catch(e) {}
    }

    return Sizzle(expr, null, null, [node]).length > 0;
};

A bit of experimentation indicates that neither the Gecko nor WebKit
versions of matchesSelector can handle div span:first so we end
up in the final Sizzle() call; note that both the Gecko and WebKit
matchesSelector variants can handle div span and your
jsfiddles work as expected in the div span case.

What does Sizzle(expr, null, null, [node]) do? Why it returns an array
containing the <span> inside your <div> of course. We'll have
this in expr:

'div span:last'

and this in node:

<div id="d2">
    <span id="s1"></span>
</div>

So the <span id="s1"> inside node nicely matches the selector
in expr and the Sizzle() call returns an array containing the
<span> and since that array has a non-zero length, the matchesSelector
call returns true and everything falls apart in a pile of nonsense.

The problem is that jQuery isn't interfacing with Sizzle properly in this case. Congratulations, you are the proud father of a bouncing baby bug.

Here's a (massive) jsfiddle with an inlined version of jQuery with a couple console.log calls to support what I'm talking about above:

http://jsfiddle.net/ambiguous/TxGXv/

A few things to note:

  1. You will get sensible results with div span and div span:nth-child(1); both of these use the native Gecko and WebKit selector engine.
  2. You will get the same broken results with div span:first, div span:last, and even div span:eq(0); all three of these go through Sizzle.
  3. The four argument version of the Sizzle() call that is being used not documented (see Public API) so we don't know if jQuery or Sizzle is at fault here.
陈独秀 2024-12-17 11:10:28

以此更新

$('.removelink').click(function(){

$(this).parent().siblings('table').find('tr:last').remove();

});

检查小提琴示例

Update with this

$('.removelink').click(function(){

$(this).parent().siblings('table').find('tr:last').remove();

});

Check Fiddle Example

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文