Страницы

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

четверг, 9 апреля 2020 г.

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

#javascript #jquery

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

    


Ответы

Ответ 1



Коротко о проблеме:

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

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

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