Почему браузеры соответствуют CSS-селекторам справа налево?

560

Селекторы CSS сопоставляются движками браузера справа налево. Поэтому они сначала находят детей, а затем проверяют своих родителей, чтобы убедиться, что они соответствуют остальным частям правила.

  1. Почему это?
  2. Это только потому, что в спецификации сказано?
  3. Влияет ли это на возможный макет, если он был оценен слева направо?

Для меня самый простой способ сделать это - использовать селекторы с наименьшим количеством элементов. Сначала идентификаторы (так как они должны возвращать только 1 элемент). Тогда, возможно, классы или элемент, который имеет наименьшее количество узлов - например, на странице может быть только один промежуток, поэтому перейдите непосредственно к этому узлу с любым правилом, которое ссылается на промежуток.

Вот несколько ссылок, подтверждающих мои претензии

  1. http://code.google.com/speed/page-speed/docs/rendering.html
  2. https://developer.mozilla.org/en/Writing_Efficient_CSS

Похоже, что это сделано таким образом, чтобы не смотреть на всех детей родителя (которых может быть много), а не на всех родителей ребенка, который должен быть одним. Даже если DOM является глубоким, он будет рассматривать только один узел на уровень, а не несколько при сопоставлении RTL. Проще / быстрее оценить CSS селекторы LTR или RTL?

tgandrews
источник
5
3. Нет - как бы вы ни читали, селектор всегда соответствует одному и тому же набору элементов.
Шиме Видас
39
Несмотря ни на что, браузер не может предположить, что ваши идентификаторы уникальны. Вы можете прикрепить один и тот же id = "foo" по всему DOM, и #fooселектор должен соответствовать всем этим узлам. У jQuery есть возможность сказать, что $ ("# foo") всегда будет возвращать только один элемент, потому что они определяют свой собственный API со своими правилами. Но браузеры должны реализовать CSS, и CSS говорит, что все в документе должно соответствовать заданному идентификатору.
Борис Збарский
4
@Quentin В «несоответствующих» (HTML) идентификаторах документов могут быть неуникальными, и в этих документах CSS требует сопоставления всех элементов с этим идентификатором. Сам CSS не предъявляет никаких нормативных требований к уникальности идентификаторов; текст, который вы цитируете, является информативным.
Борис Збарский
5
@Boris Zbarsky То, что делает jQuery, зависит от пути кода в jQuery. В некоторых случаях jQuery использует API-интерфейс NodeSelector (нативный querySelectorAll). В других случаях используется Sizzle. Sizzle не соответствует нескольким идентификаторам, но QSA соответствует (AYK). Выбор пути зависит от селектора, контекста, браузера и его версии. API запросов jQuery использует то, что я назвал «Native First, Dual Approach». Я написал статью об этом, но она не работает. Хотя вы можете найти здесь : avenbelow.ca/hosted/dhtmlkitchen/JavaScript-Query-Engines.html
Гарретт
5
Я даже не уверен, что кто-либо, кроме вас, может понять, что происходит с этим долгим разговором. У нас есть чат для этих расширенных обсуждений. Все, что вы действительно хотите сохранить, должно быть включено в вопрос или ответ, особенно если это уточняющая информация. Переполнение стека плохо обрабатывает обсуждение в комментариях.
Джордж Стокер

Ответы:

824

Имейте в виду, что когда браузер выполняет сопоставление селектора, у него есть один элемент (тот, для которого он пытается определить стиль) и все ваши правила и их селекторы, и ему нужно найти, какие правила соответствуют элементу. Это отличается от обычной вещи jQuery, скажем, когда у вас есть только один селектор, и вам нужно найти все элементы, которые соответствуют этому селектору.

Если у вас был только один селектор и только один элемент для сравнения с этим селектором, то слева направо имеет больше смысла в некоторых случаях. Но это определенно не ситуация браузера. Браузер пытается отобразить Gmail или что-то еще, и у него есть тот, который <span>он пытается стилизовать, и более 10000 правил, которые Gmail вставляет в свою таблицу стилей (я не придумываю это число).

В частности, в ситуации, когда браузер просматривает большинство селекторов, которые он считает , не соответствуют рассматриваемому элементу. Таким образом, проблема состоит в том, чтобы решить, что селектор не соответствует так быстро, как это возможно; если это требует дополнительной работы в соответствующих случаях, вы все равно выигрываете из-за всей работы, которую вы сохраняете в случаях, которые не совпадают.

Если вы начнете с простого сопоставления самой правой части селектора с вашим элементом, скорее всего, он не будет совпадать, и все готово. Если он совпадает, вам придется выполнять больше работы, но только пропорционально глубине вашего дерева, которая в большинстве случаев не так велика.

С другой стороны, если вы начнете с сопоставления самой левой части селектора ... с чем вы сравниваете его? Вы должны начать ходить по DOM, ища узлы, которые могут ему соответствовать. Просто обнаружение, что ничто не соответствует этой самой левой части, может занять некоторое время.

Так что браузеры совпадают справа; это дает очевидную отправную точку и позволяет очень быстро избавиться от большинства кандидатов на выбор. Вы можете увидеть некоторые данные по адресу http://groups.google.com/group/mozilla.dev.tech.layout/browse_thread/thread/b185e455a0b3562a/7db34de545c17665 (хотя это вводит в заблуждение), но в результате Gmail в частности два года назад для 70% пар (правило, элемент) вы могли решить, что правило не совпадает, просто изучив части тега / класса / id самого правого селектора для правила. Соответствующий показатель для набора тестов производительности загрузки страниц Mozilla составил 72%. Так что действительно стоит попытаться избавиться от этих 2/3 всех правил так быстро, как вы можете, а затем беспокоиться только о соответствии оставшихся 1/3.

Также обратите внимание, что браузеры уже делают другие оптимизации, чтобы не пытаться даже соответствовать правилам, которые определенно не будут соответствовать. Например, если у крайнего правого селектора есть идентификатор, и этот идентификатор не совпадает с идентификатором элемента, то в Gecko не будет попытки сопоставить этот селектор с этим элементом вообще: набор «селекторов с идентификаторами», которые предпринимаются исходит из поиска по хеш-таблице идентификатора элемента. Так что это 70% правил, которые имеют довольно хорошие шансы на совпадение, которые все еще не совпадают после рассмотрения только тега / класса / id самого правого селектора.

Борис Збарский
источник
5
В качестве небольшого бонуса, имеет смысл читать его RTL, чем LTR даже на английском языке. Пример: stackoverflow.com/questions/3851635/css-combinator-precedence/…
BoltClock
6
Обратите внимание, что сопоставление RTL применимо только для комбинаторов . Это не детализирует до простого уровня селектора. То есть браузер выбирает самый правый составной селектор или последовательность простых селекторов и пытается сопоставить его атомарно. Затем, если есть совпадение, он следует за комбинатором влево до следующего составного селектора и проверяет элемент в этой позиции, и так далее. Нет никаких доказательств того, что браузер читает каждую часть RTL составного селектора; на самом деле, последний абзац показывает совершенно иное (проверки идентификаторов всегда идут первыми).
BoltClock
5
На самом деле, к тому времени, когда вы сопоставляете селекторы, по крайней мере в Gecko, тэг и пространство имен стоят на первом месте. Идентификатор (а также тэг и имена классов) рассматривается на этапе предварительной фильтрации, который устраняет большинство правил, не пытаясь сопоставить селекторы.
Борис Збарский
Это может помочь в зависимости от того, какие оптимизации выполняет UA, но это не поможет этапу предварительной фильтрации в Gecko, который я описал выше. Есть второй шаг фильтрации, который работает с идентификаторами и классами, которые используются только для комбинаторов-потомков, что может помочь.
Борис Збарский
@Benito Ciaro: Не говоря уже о проблемах специфичности.
BoltClock
33

Разбор справа налево, также называемый анализом снизу вверх, на самом деле эффективен для браузера.

Учтите следующее:

#menu ul li a { color: #00f; }

Браузер сначала проверяет a, затем li, потом ulи потом #menu.

Это потому, что браузер сканирует страницу, ему просто нужно посмотреть текущий элемент / узел и все предыдущие узлы / элементы, которые он сканировал.

Следует отметить, что браузер начинает обработку в тот момент, когда он получает полный тег / узел, и ему не нужно ждать всю страницу, кроме случаев, когда он находит скрипт, и в этом случае он временно приостанавливает и завершает выполнение скрипта, а затем идет вперед.

Если это происходит наоборот, это будет неэффективно, потому что браузер обнаружил элемент, который он сканировал при первой проверке, но затем был вынужден продолжить просмотр документа для всех дополнительных селекторов. Для этого браузер должен иметь весь HTML и, возможно, должен сканировать всю страницу, прежде чем он начнет рисовать CSS.

Это противоречит тому, как большинство парсов разбирает дом. Там дом построен, и ему не нужно сканировать всю страницу, просто найдите первый элемент и затем продолжайте сопоставлять другие внутри него.

aWebDeveloper
источник
20

Это позволяет каскадно переходить от более конкретного к менее конкретному. Это также допускает короткое замыкание в приложении. Если более конкретное правило применяется во всех аспектах, к которым применяется родительское правило, все родительские правила игнорируются. Если в родительском элементе есть другие биты, они применяются.

Если бы вы пошли другим путем, вы должны отформатировать в соответствии с родителями, а затем перезаписывать каждый раз, когда у ребенка что-то другое. В конечном счете, это намного больше работы, чем игнорирование элементов в правилах, о которых уже позаботились.

Григорий Бимер
источник
11
Это отдельная проблема. Вы делаете каскадирование, сортируя правила по специфичности, а затем сопоставляя их в порядке специфичности. Но вопрос здесь в том, почему для данного правила вы подбираете его селекторы определенным образом.
Борис Збарский