Позвольте мне немного расширить мой комментарий. Все это основано на вашем втором упрощенном примере и 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/
Несколько замечаний:
- Вы получите разумных результатов с
div span
и div span:nth-child(1)
; оба они используют собственный механизм выбора Gecko и WebKit.
- Вы получите такие же разбитые результаты с
div span:first
, div span:last
и даже div span:eq(0)
; все три из них проходят через Sizzle.
- Используемая версия вызова
Sizzle()
с четырьмя аргументами не документирована (см. Public API ), поэтому мы не знаем, виновата ли здесь jQuery или Sizzle.