О том, блокируют ли JS и CSS рендеринг и синтаксический анализ DOM

HTML

  Последние системные картыHTML5Когда задействованы все теги, сортировать по<link>а также<script>При маркировке я случайно подумал о проблеме, которая меня давно беспокоит, т. е. об общем<script>помещать<body>хвостик,<link>этикетка на<head>внутри, пока страница проходитCDNКогда вводится сторонняя платформа или библиотека, это в основном<script>этикетка на<link>перед этикеткой.

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

Готов к работе

   Первая подготовительная работа, которую нужно сделать, это собрать сервер, цель – вернутьcssстиль иjsscript, и пусть сервер возвращает данные с фиксированной задержкой в ​​соответствии с переданными параметрами.

   Его структура каталогов выглядит следующим образом, гдеindex.jsа такжеstyle.cssданные, используемые для возврата,app.jsзапустить файл для сервера,index.htmlявляется файлом, используемым для тестового примера, остальные файлы или папки можно игнорировать.

├── static
│   ├── index.js
│   ├── style.css
├── app.js
├── index.html
├── package.json
├── node_modules/

   Также публикуется соответствующий задействованный код, что удобно для копирования и отладки. Необходимо пояснить, что запуск локальноnode app.jsПосле запуска браузер типаhttp://127.0.0.1:3000/может получить доступindex.html, при доступеstyle.cssможет войтиhttp://127.0.0.1:3000/static/style.css?sleep=3000sleepпараметры можно свободно контролироватьcssЗадержка возврата файлов, например, получение файлов5sустановить после возвращенияsleep=5000.

// app.js
const express = require('express')
const fs = require('fs')
const app = new express()
const port = 3000

const sleepFun = time => {
    return new Promise(res => {
        setTimeout(() => {
            res()
        }, time)
    })
}

const filter = (req, res, next) => {
    const { sleep } = req.query || 0

    if (sleep) {
        sleepFun(sleep).then(() => next())
    } else {
        next()
    }
}

app.use(filter)

app.use('/static/', express.static('./static/'))

app.get('/', function (req, res, next) {
    fs.readFile('./index.html', 'UTF-8', (err, data) => {
        if (err) return

        res.send(data)
    })
})

app.listen(port, () => {
    console.log(`app is running at http://127.0.0.1:${port}/`)
})

// static/index.js
var p = document.querySelector('p')

console.log(p)

// static/style.css
p {
    color: lightblue;
}

а потомindex.htmlпрепараты, которыеHTMLЧасть полки похожа на ту, что внизу, тут надо просто запомнитьDOMContentLoadedМероприятие будет на страницеDOMЗапускается после завершения синтаксического анализа.

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')

            console.log(p)
        })
    </script>
</head>

<body>
    <p>hello world</p>
</body>

</html>

CSS не блокирует синтаксический анализ DOM, но блокирует рендеринг DOM.

Первый вindex.htmlВставьте следующим образом<link>тег, а затем введите в браузереhttp://127.0.0.1:3000/Посетите эту страницу.

<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')

            console.log(p)
        })
    </script>
    <link rel="stylesheet" href="./static/style.css?sleep=3000">
</head>

<body>
    <p>hello world</p>
</body>

Страница    изначально пуста, и консоль выводитpэлемент при загрузке на вкладке браузераloading,3sНа оборотной стороне изображен светло-голубойhello world.

在这里插入图片描述

   Приведенная выше ситуация также показывает, чтоCSSне блокируетDOMразбор, если мы говоримCSSблокироватьDOMразобрал, тоpтеги не анализируются, поэтомуDOMне будет разобран,CSSНевозможно запустить во время процесса запросаDOMContentLoadedмероприятие. И вcssВо время запроса консоль сразу выводитpэлемент, поэтомуCSSне блокируетDOMанализ.

   Другая ситуация заключается в том, что хотяDOMанализируется очень рано, ноpМетка задерживается при отображении, потому чтоCSSСтиль не был запрошен и завершен, после того, как стиль полученhello worldвизуализируется, поэтомуCSSзаблокирует рендеринг страницы.

   кратко объясните процесс синтаксического анализа и рендеринга в браузере, синтаксический анализDOMгенерироватьDOM Tree, разборCSSгенерироватьCSSOM Tree, сочетание этих двух даетrender treeДерево рендеринга, и, наконец, браузер отображает страницу в соответствии с деревом рендеринга. Отсюда видно, чтоDOM Treeанализ иCSSOM TreeСинтаксический анализ не зависит друг от друга, и они параллельны. следовательноCSSНе блокирует страницыDOM, но из-заrender treeСборка зависит отDOM Treeа такжеCSSOM Tree, следовательноCSSпривязан к блокировкеDOMрендеринг.

Строго говоря,CSSзаблокируетrender treeгенерация, которая, в свою очередь, блокируетDOMрендеринг.

JS блокирует парсинг DOM

   Во избежание загрузкиCSSПомехи, вызванные следующим, касаются толькоJSосуществлениеforЛогика в теле цикла пока не рассматривается, просто пустьJSВыполняйте больше времени.

<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')

            console.log(p)
        })
    </script>
</head>

<body>
    <script>
        const p = document.querySelector('p')

        console.log(p)

        for (var i = 0, arr = []; i < 100000000; i++) {
            arr.push(i)
        }
    </script>
    <p>hello world</p>
</body>

   Страница доступа к браузеру, изначально пустая и распечатываемая в консолиnull, браузерloadingПосле небольшой задержки консоль печатаетpМетка отображается одновременно со страницейhello world.

在这里插入图片描述

   Вышеприведенная ситуация легко объяснимаJSзаблокируетDOMпроанализировано,JSВыполнить первоначальную консольную печатьnull, потому что в это времяpТег не проанализирован,forКогда цикл выполняется, он может ясно чувствовать, что выполнение занимает много времени и выполнение завершено.pРазбирается тег, который срабатывает в это времяDOMContentLoadedсобытие, консоль выводитpтег, пока страница отображаетсяhello world.

   Более разумное объяснение состоит в том, что, во-первых, браузер не может знатьJSКонкретное содержимое , если оно проанализировано первымDOM,на всякий случайJSУдалить все внутриDOM, то браузер занят, поэтому просто приостановите парсингDOM,Подожди покаJSПосле завершения выполнения продолжите синтаксический анализ.

CSS блокирует выполнение JS

   следующим образом на страницеJSВставить перед скриптом<link>метка и задержка3sПолучатьCSSстиль.

<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')

            console.log(p)
        })
    </script>
    <link rel="stylesheet" href="./static/style.css?sleep=3000">
    <script src="./static/index.js"></script>
</head>

<body>
    <p>hello world</p>
</body>

   пустая начальная страница, браузерloadingнагрузка3sПосле этого консоль выводитnull, затем распечатайтеpвкладка, а страница отображается голубымpЭтикетка.

在这里插入图片描述

   Такая ситуация кажетсяCSSне только заблокированDOMпарсинг, а также блокируетсяDOMоказывать.

  Но сначала подумайте, что блокируетDOMанализ, который только что был доказанCSSне блокируетDOM, поэтому можно толькоJSзаблокированDOMРазбор. ноJSВсего две строчки кода, долго не блокируется3sоколо времени. Так что есть только одна возможностьCSSзаблокируетJSисполнение.

  Таким образом, выходные результаты также могут быть грубо проанализированы, сначала проанализированы до первого<script>Этикетка,documentпривязатьDOMContentLoadedсобытие с последующим разборомlinkтег, запрос браузераCSSстиль, благодаряCSSне блокируетDOMсинтаксический анализ, поэтому браузер продолжает синтаксический анализ и находит второй<script>тег, запрос браузераJSсценарий, на этот разJSзавершиться, но из-заCSSвсе еще получаю,JSЕму нужно дождаться завершения получения, поэтому его нельзя выполнить немедленно.

   и второй<script>не может быть выполнено немедленно, что приводит к следующемуpМетка также не может быть проанализирована, причина в том,JSзаблокируетDOMРазбор. просто подожди, покаCSSПосле того, как стиль успешно получен, в это времяJSВыполнить немедленно, вывод в консольnull, затем браузер продолжает синтаксический анализ доpтеги, парсинг завершен,DOMContentLoadedИнициировано событие, вывод на консольpэтикетка, последняя светло-голубаяhello worldРендеринг на страницу.

   На самом деле это имеет смысл, представьте себеJSСодержимое скрипта должно получитьDOMэлементальCSSатрибут стиля, еслиJSхочу получитьDOMПоследний правильный стиль обязательно потребует всехCSSЗагрузка завершена, в противном случае загруженные стили могут быть неправильными или устаревшими. Так что подождите, покаJSперед сценариемCSSзагрузка завершена,JSможет быть выполнен снова, и независимо отJSПолучается в скриптеDOMСтиль элемента, это должен делать браузер.

   Вернемся к вопросу в начале статьи, так вообще<script>помещать<link>Передняя часть этикетки имеет смысл.

JS запустит рендеринг страницы

следующим образомCSSРежим на странице, где название цвета и егоrgbЗначения светло-зеленыеlightgreen(rgb(144, 238, 144)),розовыйpink(rgb(255, 192, 203)).

// index.html
<head>
    <style>
        p {
            color: lightgreen;
        }
    </style>
</head>

<body>
    <p>hello</p>
    <script src="./static/index.js?sleep=2000"></script>
    <p>beautiful</p>
    <style>
        p {
            color: pink;
        }
    </style>
    <script src="./static/index.js?sleep=4000"></script>
    <p>world</p>
    <style>
        p {
            color: lightblue;
        }
    </style>
</body>

// static/index.js
var p = document.querySelector('p')
var style = window.getComputedStyle(p, null)

console.log(style.color)

   Изначально страница отображается светло-зеленым цветом.hello, с последующим2sсделать розовыйhello beautifulи консоль печатаетrgb(144, 238, 144), а затем снова2sсветло-голубой после рендерингаhello beautiful worldи консоль печатаетrgb(255, 192, 203).

在这里插入图片描述

  Приведенные выше результаты грубо проанализированы, так как браузер сначала анализирует первый<style>этикетки иhelloтекстовыйpметка, продолжайте синтаксический анализ, чтобы найти первый<script>метка, за которой следует рендер, так как этот процесс очень быстрый, страница изначально может быть светло-зеленойhello.

   Затем браузер выдаетJSпросить,2sназадJSполучить полный запуск сейчас консольный выводrgb(144, 238, 144),JSПосле завершения операции браузер продолжает синтаксический анализ доbeautifulтекстовыйpэтикетка и вторая<style>label, а затем продолжить синтаксический анализ, чтобы найти второй<script>label, запуская рендеринг, этот процесс тоже очень быстрый, так что вы можете видеть вывод консоли и рендеринг розового цветаhello beautifulпочти одновременно.

   разрешается на второй<script>тег, браузер не делает запрос (объясните немного),2sполучено послеJSСценарий и выполнение, вывод на консольrgb(255, 192, 203), затем браузер продолжает синтаксический анализ доworldтекстовыйpэтикетка и третий<style>лейбл, в это времяDOMПосле завершения синтаксического анализа выполняется обычный рендеринг.Этот процесс также очень быстрый, поэтому вы также можете видеть вывод консоли и рендерить светло-голубым цветом.hello beautiful worldпочти одновременно.

   Теперь, чтобы ответить на вопрос только что, парсинг браузераDOM, хотя он анализирует строку за строкой, он предварительно загружает внешние ресурсы с тегами ссылок (например, с тегамиsrcотмечен<script>тег), и когда тег анализируется, нет необходимости загружать и запускать его напрямую, чтобы повысить эффективность работы. Следовательно, между двумя вышеуказанными выходными результатами будет интервал.2sситуацию, а не4s, потому что браузер предварительно загружает оба вместе<script>сценарий, первый<script>Когда скрипт загружается, второй<script>Скрипт слева2sзагрузка завершена.

   И этот вывод объясняет, почемуCSSзаблокируетJSНастоящую причину выполнения браузер не может знать заранее, конкретное содержание скрипта, поэтому при встрече<script>тег, вы должны отобразить страницу один раз, чтобы убедиться, что<script>доступно в скриптеDOMпоследний стиль. Если вы решите отрендерить страницу, то все равно останутся незагруженныеCSSstyle, вы можете только дождаться его загрузки перед рендерингом страницы.

CSS внутри тела

   Рассмотрим более частный случай.

<head>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var p = document.querySelector('p')

            console.log(p)
        })
    </script>
</head>

<body>
    <p>hello</p>
    <link rel="stylesheet" href="./static/style.css?sleep=3000">
    <p>world</p>
</body>

  По всем вышеизложенным выводам заранее анализируйте бегущие результаты, в первую очередь парсит браузер<script>сценарий,documentсвязанныйDOMContentLoadedсобытие, то браузер продолжает синтаксический анализ и обнаруживает, что текстhelloизpэтикетки и<link>тег, инициированный браузеромCSSпросьба, из-заCSSне блокируетDOMАнализируя, браузер продолжает анализировать текст какworldизpтег, на этом парсинг страницы завершен,DOMContentLoadedСобытие запускает вывод консолиpЭтикетка,3sЗадняя страница отображается светло-голубымhello world.

   Итак, изначально страница пуста и браузерloadingнагрузка3sПосле этого консоль выводитpвкладка, а страница отображается голубымhello world.

  Но на самом деле результат не такой, давайте сначала посмотримChromeпроизводительность в браузере.

在这里插入图片描述

Посмотри сноваFirefoxпроизводительность в браузере.

在这里插入图片描述

  Посмотрите позжеOperaпроизводительность в браузере.

在这里插入图片描述

  IE11и производительность следующих браузеров.

在这里插入图片描述

  Edgeпроизводительность в браузере.

在这里插入图片描述

Как это?5браузер3проявления, и ни одно из проявлений не соответствовало предварительно проанализированным.

   На самом деле причина такой разницы — так называемое мерцание браузерного стиля (FOUC,Flash of Unstyled Content)Феномен.

  Из-за несоответствия архитектуры движка браузера браузер парситBodyвнутриCSS, движок браузера может сделать выбор.

   Один из вариантов – это может бытьCSSприостановить последующее наблюдениеDOMанализ, вCSSПродолжить синтаксический анализ после завершения загрузки, эта ситуация вызоветCSSблокироватьDOMпарсинг, в результате чего страницаDOMПарсинг и отрисовка его стиля задерживаются, и это представление такое же, какJSситуация похожая.

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

   Это резюме не должно быть слишком запутанным, просто помнитеBodyвнутриCSSИз-за различий в браузерах будут представлены разные формы представления, и это явление обычно называютFOUC, код должен попытаться избежать этой ситуации.

В итоге

   Суммируя все вышеизложенное, можно сделать следующие выводы.

  • CSSне блокируетDOMРазобрать, но заблокируетDOMРендеринг, более строго,CSSзаблокируетrender treeгенерация, которая, в свою очередь, блокируетDOMоказание
  • JSзаблокируетDOMРазобрать
  • CSSзаблокируетJSисполнение
  • браузер встречает<script>этикетка и нетdeferилиasyncсвойство вызовет отрисовку страницы
  • Bodyвнутренняя ссылкаCSSболее особенный, сформируетFOUCявление, пожалуйста, используйте с осторожностью

🎉 Напишите в конце

🍻Ребята, если вы это видели и считаете, что эта статья была вам полезна, ставьте лайк 👍 илиStar✨Поддержите!

Кодирование вручную, если есть ошибки, исправьте их в комментариях 💬~

Ваша поддержка — самая большая мотивация для меня обновляться💪~

GitHub,Blog,Наггетс,CSDNСинхронизированное обновление, подписывайтесь 😉~