Как известно, jQuery.fn.find по возможности пытается скормить селектор браузерному querySelectorAll
При этом есть вариант, когда для некоторого элемента генерируется id, после чего браузеру достаётся селектор, в начало которого добавилась преверка на этот id.
Зачем вообще нужен этот фокус разобрался. Селектор ставится в соответствие не внутри элемента, а вообще в документе. Просто фильтруется поддерево.
Вопрос зачем делается выборка по атрибуту вместо id
Т. е. почему nid = "[id='" + nid + "'] ";, а не nid = "#" + nid + " ";?
Более полный контекст для двух версий приведён далее:
1.8.1
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
var groups, i, len,
old = context.getAttribute("id"),
nid = old || expando,
newContext = rsibling.test( selector ) && context.parentNode || context;
if ( old ) {
nid = nid.replace( rescape, "\\$&" );
} else {
context.setAttribute( "id", nid );
}
groups = tokenize(selector, context, xml);
// Trailing space is unnecessary
// There is always a context check
nid = "[id='" + nid + "']";
for ( i = 0, len = groups.length; i < len; i++ ) {
groups[i] = nid + groups[i].selector;
}
try {
push.apply( results, slice.call( newContext.querySelectorAll(
groups.join(",")
), 0 ) );
return results;
} catch(qsaError) {
} finally {
if ( !old ) {
context.removeAttribute("id");
}
}
}
1.11.3
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
groups = tokenize( selector );
if ( (old = context.getAttribute("id")) ) {
nid = old.replace( rescape, "\\$&" );
} else {
context.setAttribute( "id", nid );
}
nid = "[id='" + nid + "'] ";
i = groups.length;
while ( i-- ) {
groups[i] = nid + toSelector( groups[i] );
}
newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
newSelector = groups.join(",");
}
Ответ
Коротко о проблеме:
JS:
var foo = document.getElementById("foo"); console.log( foo.querySelectorAll('div span').length ); // 1
Проблема querySelectorAll в том, что сначала он вытащит все что подходит под селектор div span начиная с корня документа, а потом отфильтрует полученный результат на вхождение в элемент-контекст (foo) - т.е. селектор div span найдет один span и да - этот span находится внутри #foo
Трюк с аттрибутами используется как раз для того, чтобы исключить элемент контекста и начинать запрос сразу с корня документа, избегая такой странной выборки. Небольшое пояснение кода:
if ( (old = context.getAttribute("id")) ) { nid = old.replace( rescape, "\\$&" ); } else { context.setAttribute( "id", nid ); }
Если у элемента-контекста есть id, то используем его, а иначе добавляем ему id с уникальным только что созданным хешем.
nid = "[id='" + nid + "'] ";
Преобразуем в селектор аттрибута, благодаря операции выше мы уверены, что у элемента-контекста всегда будет id - либо уникальный либо сгенерированный.
i = groups.length; while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); }
Группы - это если селекторов несколько (перечислены через запятую). Здесь в начало каждой группы добавляется селектор аттрибута. Если брать оригинальный пример с запросом foo.querySelectorAll('div span'), то группа в данном случае это div span, а после добавления аттрибута получится [id="foo"] div span. Пре передаче этого нового селектора в querySelectorAll запрос будет выполнен уже в рамках элемента-контекста не смотря ни на что.
Используются аттрибуты [id="foo"] вместо #foo, т.к. идентификатор по спецификации может сожержать спецсимволы, которые очень сложно эскейпить самостоятельно, и в команде было принято решение скинуть парсинг идентификатора на браузер.
Источники:
John Resig - Thoughts on querySelectorAll jquery/sizzle - Replace ID-attribute selectors with ID-selectors where possible #354 jquery/sizzle - Bug #7533. Fix for Element-rooted queries where ID contains CSS3 selector meta characters jquery/sizzle - Bug #7533. Fix for IDs which contain CSS3 selector meta characters #37
Комментариев нет:
Отправить комментарий