Как парсится CSS селектор: слева направо или справа налево
Что проверяют
Знаете ли вы, как браузер сопоставляет селекторы с элементами, и можете ли объяснить, почему именно так. Следом спрашивают, какие селекторы на практике дорогие и заметно ли это в современных браузерах.
Как отвечать
Разделите две вещи. Сам текст CSS токенизируется и разбирается слева направо, как любой другой. А сопоставление селектора с элементом идёт справа налево: браузер начинает с самой правой части, ключевого селектора, и только потом поднимается к предкам.
Почему так: при расчёте стилей браузер идёт от элемента и отвечает на вопрос «какие правила к нему подходят». Для .menu li a он берёт конкретную ссылку, проверяет, что это a, и лишь тогда ищет среди предков li, а выше .menu. Большинство правил отсеивается на первом же шаге, потому что правая часть не совпала. Если идти слева, пришлось бы для каждого .menu обходить всё поддерево.
Движки добавляют оптимизации поверх этого:
- хэширование правил: правила раскладываются по корзинам по
id, классу или тегу правой части, и элемент проверяется только против подходящих корзин; - фильтр Блума по предкам: быстро отвечает, что нужного класса среди предков точно нет, без обхода дерева;
- инвалидация: при смене класса стили пересчитываются не для всей страницы, а только для затронутых элементов.
Исключение из общей схемы это :has(): он смотрит вниз, на потомков или следующих соседей, поэтому движкам пришлось строить для него отдельную логику инвалидации. На больших деревьях им лучше не злоупотреблять.
Практический вывод: дорогие селекторы те, где правая часть широкая, например .sidebar *, и длинные цепочки потомков. Но сегодня узкое место обычно не в сопоставлении, а в размере DOM и частых перерасчётах layout.
Где спотыкаются
Отвечают «слева направо», перенося порядок чтения на порядок сопоставления.
Путают сопоставление со специфичностью и каскадом. Порядок проверки частей селектора никак не влияет на то, какое правило победит.
Уходят в микрооптимизацию: переписывают всё на одиночные классы ради скорости, не посмотрев на вкладке Performance в DevTools, сколько на самом деле занимает Recalculate Style. Обычно тормозит другое.