Псевдоселекторы с методом братьев и сестер - PullRequest
4 голосов
/ 19 октября 2011

Ранее я ответил на этот вопрос , который в основном касался удаления строки таблицы. Этот вопрос возник в результате комментариев по этому вопросу. Учитывая следующий 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), которые являются последними tr в пределах table». Очевидно, что нет элементов, которые соответствуют этому описанию - в 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 для отслеживания этой проблемы.

Ответы [ 2 ]

5 голосов
/ 22 октября 2011

Позвольте мне немного расширить мой комментарий. Все это основано на вашем втором упрощенном примере и 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.

Это оставляет нам все наши родственные элементы DOM в ret и Теперь посмотрим на фильтрацию:

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])? Почему он возвращает массив содержащий <span> внутри вашего <div>, конечно. У нас будет это в expr:

'div span:last'

и это в node:

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

Так что <span id="s1"> внутри node точно соответствует селектору в expr и вызов Sizzle() возвращает массив, содержащий <span> и поскольку этот массив имеет ненулевую длину, matchesSelector call возвращает true и все разваливается на кучу глупостей.

Проблема в том, что в этом случае jQuery не взаимодействует с Sizzle должным образом. Поздравляю, ты гордый отец прыгающего младенца.

Вот (массивный) jsfiddle с встроенной версией jQuery с парой console.log вызовов для поддержки того, о чем я говорю выше:

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

Несколько замечаний:

  1. Вы получите разумных результатов с div span и div span:nth-child(1); оба они используют собственный механизм выбора Gecko и WebKit.
  2. Вы получите такие же разбитые результаты с div span:first, div span:last и даже div span:eq(0); все три из них проходят через Sizzle.
  3. Используемая версия вызова Sizzle() с четырьмя аргументами не документирована (см. Public API ), поэтому мы не знаем, виновата ли здесь jQuery или Sizzle.
0 голосов
/ 19 октября 2011

Обновление с этим

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

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

});

Проверка Пример Fiddle

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...