Страницы

Поиск по вопросам

вторник, 25 июня 2019 г.

Почему jQuery использует id как атрибут для querySelectorAll?

Как известно, 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

Комментариев нет:

Отправить комментарий