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

И, честно говоря, в 99% случаев, когда люди говорят такую ​​хрень, они хлопают задницей по ветру.

Единственный раз, когда это может иметь смысл - использование innerHTML - это даже не правда. И знаешь, что? Просто прекратите использовать этот устаревший, небезопасный список обломков поезда о том, как НЕ добавлять что-то на страницу ... Или, по крайней мере, как не делать этого во всех, кроме нескольких угловых случаев, когда скорость не имеет значения, а вы знать, что содержание безопасно.

Есть ряд законных причин для использования фрагментов документа, но такие вещи, как «теневой DOM», кажутся пустой тратой времени и кода, если вы понимаете, как работают браузеры, и знаете одну простую, маленькую, крошечную деталь. Деталь, которая показывает, что многие утверждения и идеи являются полной ерундой.

Изменения DOM в браузере не отображаются до тех пор, пока не будет запущен скрипт!

Ничего, нада, zip, zilch, nein! Пока выполнение сценария не остановится, а затем управление вернется к браузеру, ваши изменения в DOM ничего не сделают с точки зрения запуска повторного применения CSS или рендеринга. Почему это такая важная деталь?

Вы слышали, как люди говорили, что вам следует «вносить изменения в DOM»? Или «не писать в DOM, потому что вы еще не хотите, чтобы рендеринг произошел»? Как насчет «сделать все изменения последними, если они будут быстрее»?

БУЛЛ-ПЕЧЕНЬЕ! Все подобные утверждения - полная чушь. Потому что снова:

Браузеры не отображают изменения DOM по сценарию, пока после выполнения сценария не передается управление!

Давайте это докажем!

Самый простой способ, который я могу придумать, - это просто извергнуть кучу новых абзацев, заполненных текстом lorem-ipsum в достаточном количестве (32 768 должно хватить), и мы сможем получить значительную производительность .now () start, после завершения записи DOM, а затем после завершения рендеринга. Маленький, простой, легко отслеживаемый.

Данные, которые нужно вставлять снова и снова, я буду хранить в массиве следующим образом:

var testData = [
 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et mauris odio. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam tortor urna, dapibus sit amet dolor a, fringilla dapibus mi. Integer tempus rutrum risus vel hendrerit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc sit amet convallis nulla, sit amet fermentum leo. Nullam porta, magna nec vehicula efficitur, orci nibh dictum arcu, a bibendum massa leo vitae lacus. Curabitur et sem at enim dictum dictum sit amet ut ligula. Etiam eget suscipit tellus. Donec et ligula id justo tincidunt bibendum. Pellentesque vitae egestas tortor. Aliquam ut vulputate arcu, nec sollicitudin augue.",
 "Sed a vulputate tellus, id dapibus dui. Donec eget pretium orci. Etiam ullamcorper efficitur auctor. Praesent gravida mi id velit rutrum lobortis. Proin sit amet erat eu mi ultricies ornare. Vivamus malesuada volutpat fermentum. Suspendisse viverra mattis risus eget vehicula. Mauris faucibus ipsum eget dictum accumsan. Fusce dictum viverra diam a feugiat. In non fermentum sem. Curabitur at nulla non neque mollis aliquam. Aliquam libero orci, cursus sit amet aliquam ac, pharetra ut ligula. Vestibulum placerat leo vitae dui varius, ac malesuada tortor viverra. Aenean faucibus mi vitae nunc ornare, et consequat quam tempor. Vestibulum vitae scelerisque quam, vitae luctus neque.",
 "Duis felis augue, facilisis sed tempus et, consectetur in justo. Integer libero elit, interdum et pharetra et, finibus a ipsum. Nulla in ex ac lectus hendrerit finibus. Suspendisse condimentum tristique laoreet. Fusce urna neque, mattis et sollicitudin a, feugiat at sapien. Sed ultrices ex id mauris dignissim interdum. Nullam vel sem ac risus malesuada pharetra. Ut et malesuada turpis. Proin placerat at nisl in finibus. Proin tristique, nunc et facilisis varius, sapien ipsum scelerisque est, et molestie nisi massa eu risus. Nam id orci quis orci lobortis ornare. Etiam vitae consectetur nisl. Donec vehicula auctor turpis. Integer venenatis feugiat finibus."
];

Но ждать!?!? Вы не можете отследить, когда рендеринг завершен!

Нет ... больше злобных слов от людей, которые не думают. Это все потому, что независимо от того, насколько JavaScript обманывает вас, заставляя ДУМАТЬ, что он может выполнять асинхронные задачи, браузеры по-прежнему остаются - или, по крайней мере, вкладка браузера - однопоточными! Различные подкомпоненты движка браузера - синтаксический анализатор, сценарий, средство визуализации - НЕ МОГУТ выполняться одновременно. Честно говоря, если бы люди перестали бороться с этим и / или осознали это, их код был бы намного проще и менее запутанным. Посмотрите на ошеломляющий идиотский обруч огненного мусора, через который прыгают React и Vue.

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

setTimeout(function() {
  console.log('ended: ', performance.now());
}, 1);

Если вы установите ноль, он запускается перед рендерингом «изредка». Но если мы установим его на 1 мс, этот тайм-аут не сработает до тех пор, пока рендеринг не вернет управление конвейеру.

Следовательно, простой тест с использованием прямого доступа к DOM выглядит примерно так:

(function(d) {
  function writeResult(txt) {
    results.appendChild(d.createTextNode(txt + "\r\n"));
  }
 
  var
    results = d.body.appendChild(d.createElement('pre')),
    start = performance.now();
  
  
  for (var i = 0; i < 0x8000; i++) {
    d.body.appendChild(d.createElement('p')).textContent = testData[i % 3];
  }
 
  var domWriteComplete = performance.now();
 
  setTimeout(function() {
    var renderComplete = performance.now();
    writeResult(navigator.userAgent);
    writeResult("   DOM Complete: " + (domWriteComplete - start));
    writeResult("Render Complete: " + (renderComplete - start));
   }, 1);

})(document);

Чтобы протестировать фрагменты документа, мы просто меняем добавление таким образом:

  var
    results = d.body.appendChild(d.createElement('pre')),
    start = performance.now(),
    fragment = d.createDocumentFragment();
 
  for (var i = 0; i < 0x8000; i++) {
    fragment.appendChild(d.createElement('p')).textContent = testData[i % 3];
  }
 
  d.body.appendChild(fragment);

Shadow DOM (только хром) представляет собой похожий простой обмен:

  var
    results = d.body.appendChild(d.createElement('pre')),
    testArea = d.body.appendChild(d.createElement('div')),
    start = performance.now(),
    shadow = testArea.createShadowRoot();
  
  for (var i = 0; i < 0x8000; i++) {
    shadow.appendChild(d.createElement('p')).textContent = testData[i % 3];
  }
 
  var domWriteComplete = performance.now();

Обратите внимание, что начало выполняется раньше, чем что-либо между ними отличается. С помощью теневого DOM я создаю DIV для подключения вещей, поскольку первая запись в shadowRoot стирает все содержимое родительского объекта, чего мы здесь не хотим.

Я запускаю каждый из них на отдельной вкладке, чтобы ничто другое не мешало, и принимаю результат третьего ctrl-F5 в качестве значимых значений, чтобы избавиться от необычного поведения браузера и памяти. Я не запускаю их на одной вкладке, так как первые несколько моментов запуска скрипта часто быстрее, чем позже в скрипте (внутреннее кеширование / какая-то оптимизация?). По сути, мы хотим устранить все возможные помехи нашим результатам.

Затем, чтобы быть уверенным, я протестирую их в Firefox и Chrome, хотя Firefox не имеет реальной теневой модели DOM, так что ей придется отсидеться от нее.

Полный архив тестов можно найти здесь:
https://cutcodedown.com/for_others/medium_articles/shadowDOM/shadowDOM.rar

Результаты теста

Более 20 прогонов, отбрасывая очевидные «выбросы», которые возникают из-за событий, скорее всего, не в браузерах, я получил следующие средние значения на Ryzen 5 3600.

              DOM Complete    Render Complete
FIREFOX
Direct DOM         43              1092
Doc Fragment       45              1107
CHROME
Direct DOM         31              4876
Doc Fragment       38              5017
Shadow DOM         32              4885

Не говоря уже о том, что FF медленнее при написании скриптов / объектов и быстрее при рендеринге, мы можем сделать один вывод. В частности, если кто-то принимает идею о том, что значение менее 50 мс при «завершении рендеринга», вероятно, находится в пределах нашей погрешности.

ЭТО НЕ ИМЕЕТ РАЗНИЦЫ!

Непосредственное написание DOM не имеет значения, потому что это объекты. Объекты обычно находятся на низком уровне, на который ссылаются указатели, поэтому их изменение ничем не отличается, независимо от того, к чему они прикреплены. Более того, изменение значений объекта оказывает НУЛЕВОЕ влияние на его братьев и сестер, потомков или родителей.

Нет никаких причин даже думать, что прямая работа с живым DOM может что-то изменить. Тем не менее, люди ВСЕ ЕЩЕ продолжают говорить об этом.

Это также показывает, что мы видим СЕКУНДЫ времени между временем завершения DOM и выполнением тайм-аута. Это время применения рендеринга / стиля. Мы МОЖЕМ поймать его! И, как мы также видим, идея о том, что вы увеличиваете скорость, с которой пользователь сможет использовать то, что вы сделали, - это просто еще одна лысая ЛОЖЬ! Следовательно, «вам нужно внести изменения» - это ЛОЖЬ.

Где это может иметь значение?

Эти утверждения и предположения - даже если они абсолютная чушь - не приходят ниоткуда по волшебству. Я подозреваю, что в большинстве случаев это либо сочетание плохих практик, либо пара небольших угловых случаев и их несоразмерность. Как говорилось в моей предыдущей статье Прекратите превращать избегание в никогда », иногда люди слышат небольшую деталь для редкого случая и накладывают ее на все, имеет ли это смысл или нет.

Угловой кейс №1, InnerHTML - ЛОЖЬ!

Мусорный неаккуратный ментальный карлик типа "eye cans haz teh intarwebs" - это innerHTML - можешь сказать, что я не фанат? - остается одним из наиболее распространенных случаев, когда это действительно имеет значение. Element.innerHTML означает, что парсер должен участвовать в процессе, поэтому давайте добавим еще два теста.

Проблема в том, что у documentFragment нет innerHTML, поэтому мы застряли, сравнивая только прямой innerHTML:

(function(d) {
  start = performance.now();
  
  for (var i = 0; i < 0x800; i++) {
    d.body.innerHTML += "<p>" + testData[i % 3] + "</p>";
  }
 
  var domWriteComplete = performance.now();
  setTimeout(function() {
    var renderComplete = performance.now();
    console.log(navigator.userAgent);
    console.log("   DOM Complete: ", (domWriteComplete - start));
    console.log("Render Complete: ", (renderComplete - start));
  }, 1);
 
})(document);

К shadowRoot:

(function(d) {
  var
    testArea = d.body.appendChild(d.createElement('div')),
    start = performance.now(),
    shadow = testArea.createShadowRoot();
    
  for (var i = 0; i < 0x800; i++) {
    shadow.innerHTML += '<p>' + testData[i % 3] + '</p>';
  }
  
  var domWriteComplete = performance.now();
  
  setTimeout(function() {
    var renderComplete = performance.now();
    console.log(navigator.userAgent);
    console.log("   DOM Complete: ", (domWriteComplete - start));
    console.log("Render Complete: ", (renderComplete - start));
  }, 1);
  
})(document);

Обратите внимание, что я пробегаю 1/10 числа петель. Это связано с тем, что innerHTML настолько медленный, что браузер начнет жаловаться, что сценарий работает слишком долго, искажая наши результаты. Я также переключился на console.log, потому что запись в innerHTML может испортить ссылку на сгенерированный «PRE».

Мы также могли бы использовать innerHTML как DIV вместо фрагмента документа, но результат был почти идентичен работе непосредственно с телом.

              DOM Complete    Render Complete
CHROME
body.innerHTML    14729           15302
shadow.innerHTML  14771           15288

Опять же, среднее значение за 20 запусков в основном находится в пределах нашей погрешности ...

Так что фраза «innerHTML медленно работает в реальном DOM» ТАКЖЕ фикция, так что это НЕ ЭТО!. Применение его к тени или неактивному элементу не обеспечивает НИКАКОГО реального ускорения времени рендеринга или выполнения скрипта!

Да, innerHTML примерно в 30 раз медленнее, чем работа непосредственно с DOM, это одна из МНОГИХ причин, по которым вы не должны использовать его в первую очередь.

Угловой шкаф №2, «Комплектующие» - ГЛУПОЙ!

Это еще один из многих кусочков модного мусора, над которым фанаты JavaScript натирают свои трусики, и, честно говоря, вся эта идея является еще одним памятником глупости.

На внутренней стороне они используют такие вещи, как JSX, чтобы извергнуть то, что выглядит как разметка, которая компилируется в объект, отправляемый на стороне клиента для добавления в DOM. Этот аспект кажется костылем для людей, слишком глупых, чтобы работать с объектами - не говоря уже о JavaScript - что всегда заставляет меня спрашивать: «Почему бы просто не использовать разборчивый объект через что-то вроде JSON?»

Но со стороны клиента эти «компоненты» являются одними из самых глупых, о чем я могу думать. Почему я это говорю? Потому что, если вы хотите обновить какое-либо значение / текст внутри структуры, они перезаписывают весь объект в DOM.

Как это объяснить… допустим, у вас есть корзина покупок, которая в обычном HTML выглядит примерно так:

<table id="cart">
  <caption>Shopping Cart</caption>
  <thead>
    <tr>
      <th scope="col">Item Name</th>
      <th scope="col">Quantity</th>
      <th scope="col">Total</th>
    </tr>
  </thead><tbody>
    <tr>
      <th scope="row">Eb Alto Saxophone, Chinese</th>
      <td>1</td>
      <td>$239.99</td>
    </tr><tr>
      <th scope="row">Mahaya 5C Mouthpiece</th>
      <td>1</td>
      <td>29.99</td>
    </tr><tr>
      <th scope="row">Leather Eb Alto Ligature</th>
      <td>1</td>
      <td>14.99</td>
    </tr>
  </tbody><tfoot>
    <tr>
      <th scope="row" colspan="2">
        Shipping<br>
        <em>Free for orders over $100</em>
      </th>
      <td>0.00</td>
    </tr><tr>
      <th scope="row" colspan="2">
        Grand Total:
      </th>
      <td><strong>284.97</strong></th>
    </tr>
  </tfoot>
</table>

Если вы используете что-то вроде реакции, вы замените все различные TR их собственными «компонентами», которые содержат в основном ту же разметку, но с переменными вместо данных. Затем эти компоненты будут подключены к компоненту, представляющему таблицу и ее четырех дочерних элементов (caption, thead, tbody, tfoot). Внешний компонент превращается в дерево объектов, которое отправляется на стороне клиента.

Проблема в том, когда / если пользователи хотят изменить значения на стороне клиента. Допустим, пользователь может изменить количество лигатур с 1 на 3. Вам нужно будет пересчитать и изменить итоговое значение для этой строки и итоговую сумму, верно?

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

Что будет быстрее: воссоздать всю структуру DOM во фрагменте или тени и снова подключить ВСЕ переменные или просто пройти DOM к соответствующим узлам и подключить значения в реальном времени?

Нет смысла тратить время на воссоздание всей этой проклятой штуки и замену существующей только для того, чтобы изменить значение в двух полях, но это ТОЧНО то, что системы, подобные React и Vue, делают со своими компонентами / представлениями chazerei.

И почему? Потому что люди, которые придумали это безумие бекаптах, находятся в некотором бредовом восприятии, что работать непосредственно с DOM «неправильно», «медленно», «имеет проблемы» или что это как-то «сложнее».

Все подобные заявления держат воду как стальное решето. Единственное, что я могу понять, это то, что эти клоуны просто никогда не выучили достаточно HTML или JavaScript, чтобы понять, как использовать DOM, или они проглотили кучу лжи, крючок, леску, грузило и немного прута!

Печально то, что единственная часть этой методологии, которая выдерживает критику, заключается в том, что вы можете работать с базовыми переменными для выполнения вычислений без необходимости вручную обновлять элементы на странице ... но это может быть воспроизведено без использования DOM или неэффективного использования просто создавая структуры функций / объектов с помощью геттеров / сеттеров, которые автоматически обновляют ТОЛЬКО DOM для вас, когда вы меняете что-то значение!

Вот как я уже полтора десятилетия решаю такие проблемы, используя Object.defineProperty. Я знаю, я знаю ужасы ... Я уже слышу, как фанаты «класса» выстраиваются в очередь, чтобы кричать, насколько динамично расширяющиеся объекты - это «зло». К черту этот шум.

Возможно, это хорошая тема для другой статьи.

Угловой случай № 3 «Выполнение вне очереди / асинхронные операции» - Почему?

Эта идея исходит как от функциональных программистов, так и от фанатов объектных фреймворков, когда через минуту они скажут: «Не храните информацию напрямую в DOM», а через минуту они скажут: «Мы хотим поставить в очередь изменения в DOM, чтобы мы все еще можем ссылаться на живую копию параллельно с нашей новой »… ЧТО ЭТО? Вот где эти педантичные дураки совершенно теряют меня, поскольку они часто в одном абзаце, а иногда даже в одном предложении противоречат самим себе.

Может быть, это всего лишь я ... но если я напишу код, который изменяет DOM сейчас, то это потому, что я хочу сделать это сейчас. Если я не хочу делать это сейчас, зачем мне это делать сейчас? Я до сих пор не могу понять, почему они говорят о вещах сейчас, а о том, что это происходит позже. Вы хотите сделать это сейчас, объявите это сейчас. Тогда вы хотите это сделать, тогда объявите это. Нет смысла заявлять об этом сейчас, чтобы сделать это ... скоро.

По сути, это сбивающая с толку круговая логика, которая приводит к тому, что люди, использующие мусор, например vue, react, angular и т. Д., Полагаются на три раза больше кода, необходимого для выполнения работы, прежде чем они даже напишут свой собственный код, поэтому они могут написать в два-десять раз больше код, который они должны были использовать с самого начала! Тем более все лишние бессмысленные уровни сложности и тратящие время шаги, связанные с их «процессом сборки».

И они не могут защитить себя

Сверху вниз эти утверждения - такая глупая тарабарщина, но почему-то те, кто их извергает, не замечают этого. Как и большинству культистов, когда им предъявляют факты, противоречащие их риторике, они не могут защитить их, поэтому отвечают такими потрясающими жемчужинами, как:

  1. Вы просто новичок, который не научился
  2. Вы просто старожил, который не изменится
  3. Так и так в индустрии так сказали
  4. Все так делают
  5. Это просто так.
  6. Дело не в том, что вы сказали, а в том, как вы это сказали

Обратите особое внимание на то, что все это отклонения, совершенно отсутствующие в каком-либо разумном факте. И все же они ВСЕГДА являются первой проклятой вещью из уст тех, кто защищает эту ЛОЖЬ. «Фреймворки - это хорошо», «вам следует использовать эту технику, которая требует вдвое большего объема кода», «это проще» или «лучше для совместной работы». ЛОЖЬ !!!

Первые два отклоняют мессенджер, чтобы избежать сообщения. Это фаворит людей, которые ДУМАЮТ, что они эксперты, но на самом деле немногим больше, чем культисты. Они ухватываются за любые оправдания и выдвигают дикие обвинения - часто противоречивые - просто потому, что «вау-вау, eye dunz wunna heer eet!»

Третий бессмысленно анекдотичен, четвертый - это просто заблуждение, ставшее широко распространенным, а последнее - это плакса «Вау-вау, из-за чего-то!».

Пятый и шестой - это умышленное отрицание и попытка чего-либо, кроме логического опровержения по существу. Это когда они бегут к модераторам на форумах или пытаются организовать толпу, чтобы выкрикнуть правду, которую они не хотят слышать, не могут защищаться и в целом делают то, чего они не могут терпеть… быть разоблаченными за МОШЕННИЦЫ, которыми они являются на самом деле!

Все это мы снова и снова видим от профессиональных преподавателей, фанатов, создателей фреймворков и других людей, которые поверят в первую хорошую чушь, которая войдет в дверь, даже не посмотрев на факты. ДЖО ЗАПРЕЩАЕТСЯ предлагать людям больше узнать об основных языках и процессах. Нет, это все та же раздражительная позиция «сейчас, сейчас, сейчас» и «я, я, я, ебать факты, ебать всех остальных», которые являются неотъемлемой частью современного общества.

Отношение, с которым мне приходилось иметь дело почти с каждым клиентом, с которым я консультировался за последние полтора десятилетия. Всегда найдется по крайней мере человек, который отбрасывает такое отношение, даже когда компания / организация / агентство испытывают серьезные проблемы. Эти люди настолько уверены, что «структура не может быть неправильной» и что у них есть «хорошо протестированные компоненты» и вся прочая ерунда, которую их накормили и научили бездумно повторять.

Даже когда они получают поток электронных писем от пользователей, сообщающих о проблемах. Даже если их трафик незначителен, чем должен быть. Даже когда люди жалуются, что они не могут даже использовать контактные формы, потому что они слишком полагаются на сценарии без изящного плана деградации. Даже когда их веб-сайты представляют собой не что иное, как бушующие пожарные ямы. ДАЖЕ КОГДА ОНИ ПРЕДСТАВЛЯЮТСЯ В СУД за недостаток доступности ... Также нанимают консультантов вроде меня, чтобы помочь исправить это!

Всегда есть некоторые разработчики или высшее руководство, которые говорят «нет-нет-нет-нет-нет», например, отвергают Muppet «Yip Yups» из-за того, что им говорят, что именно то, что им сказали, и то, что они выбрали, является причиной их проблем. Исключительно потому, что были проданы эти виды пяти и десяти вуду.

В двух словах о когнитивном диссонансе.

Заключение

Работа непосредственно с DOM не является «злом» или «неправильным», и, черт возьми, она не медленнее. Фактически, если вы потратите чертовски много времени, чтобы понять DOM и действительно попробовать поработать с ним, вы могли бы понять, насколько полно песка вы набиты предполагаемыми «экспертами» с их тупыми невежественными «фреймворками» и связанное с ним худу из магазина dimestore.

Утверждения об обратном, похоже, существуют и выдвигаются людьми, использующими те же самые мусорные фреймворки, против которых я всегда ругаюсь, и которые вынуждены вырывать оптом с веб-сайтов клиентов. Практики и мировоззрение, ни в коем случае не основанные на фактах, которые ничего не делают, кроме как усложняют работу, требуют больше времени для развития и создают королевскую боль для поддержания; несмотря на дикие необоснованные заявления о том, насколько «легче» или «лучше для сотрудничества» невежественные фанаты-ничего не знают, скандируют, попугай и восхваляют до небес.

«Но миллионы сайтов используют это!» ты плачешь ... миллионы людей думают, что земля плоская, апельсиновый сыр - действительно стабильный гений, что пихание нефритовых яиц в язу полезно для здоровья женщин, что в органических продуктах используется меньше пестицидов и они волшебным образом «безопаснее» или «здоровее» », И что в небе есть магический маньяк-геноцид, угрожающий нам вечными муками… потому что он« любит нас ».

Не делает ни одного правильного. Это популярная ошибка и один из многих способов заставить тупицы поверить в ложь.

… и из лжи никогда не получалось ничего хорошего.

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