Почему "element.innerHTML + =" плохой код?

Мне сказали не добавлять файлы с помощью element.innerHTML += ... следующим образом:

var str = "<div>hello world</div>";
var elm = document.getElementById("targetID");

elm.innerHTML += str; //not a good idea?

Что не так с этим?, какие у меня другие альтернативы?

Ответ 1

Каждый раз, когда устанавливается innerHTML, HTML должен быть проанализирован, DOM создан и вставлен в документ. Это требует времени.

Например, если elm.innerHTML имеет тысячи div, таблиц, списков, изображений и т.д., то вызов .innerHTML += ... приведет к тому, что синтаксический анализатор снова проанализирует все эти вещи. Это также может сломать ссылки на уже построенные элементы DOM и вызвать другой хаос. В действительности, все, что вы хотите сделать, это добавить один новый элемент в конец.

Лучше просто вызвать appendChild:

var newElement = document.createElement('div');
newElement.innerHTML = '<div>Hello World!</div>';
elm.appendChild(newElement);​​​​​​​​​​​​​​​​

Таким образом, существующее содержимое elm снова не обрабатывается.

ПРИМЕЧАНИЕ.. Возможно, [некоторые] браузеры достаточно умны, чтобы оптимизировать оператор +=, а не повторно анализировать существующее содержимое. Я не исследовал это.

Ответ 2

Да, elm.innerHTML += str; это очень плохая идея.

Типичный ответ "браузер должен перестроить DOM" действительно не оправдывает себя:

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

  2. Тогда у вас есть длинная строка, к которой вы добавляете. Этот шаг в порядке.

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

  4. Затем он анализирует строку, строит из всех уничтоженных элементов, свойств и узлов, чтобы создать новый фрагмент DOM, который в основном идентичен.

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

  6. Но это еще не сделано! Браузер также должен перезапускать старые узлы путем сканирования всех переменных JavaScript.

Проблемы:

  • Некоторые свойства могут не отражаться HTML, например, текущее значение <input> будет потеряно и сброшено к исходному значению в HTML.

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

  • Если ваш js-код ссылается на какие-либо старые узлы, они не будут уничтожены, а вместо этого будут потеряны. Они принадлежат документу, но больше не находятся в дереве DOM. Когда ваш код обращается к ним, ничего не может произойти, или это может вызвать ошибку.

  • Обе проблемы означают, что это плохо работает с плагинами js - плагины могут присоединять обработчики или запоминать старые узлы и вызывать утечку памяти.

  • Если вы привыкли выполнять манипуляции с DOM с помощью innerHTML, вы можете случайно изменить свойства или заняться другими вещами, которые вам не нужны.

  • Чем больше у вас узлов, тем больше это неэффективно, тем больше батарейного сока даром.

Короче говоря, это неэффективно, это подвержено ошибкам, это просто ленивый и неосведомленный.


Лучшая альтернатива - Element.insertAdjacentHTML, которую я не видел в других ответах:

elm.insertAdjacentHTML( 'beforeend', str )

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

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

Если кто-то скажет вам, что это не кросс-браузер, это настолько полезно, что он является стандартом HTML5 и доступен во всех браузерах.

Никогда не пиши elm.innerHTML+= снова.

Ответ 3

Альтернативой является .createElement(), .textContent и .appendChild(). Добавление с помощью += является проблемой только в том случае, если вы имеете дело с большим количеством данных.

Демо: http://jsfiddle.net/ThinkingStiff/v6WgG/

Script

var elm = document.getElementById( 'targetID' ),
    div = document.createElement( 'div' );
div.textContent = 'goodbye world';
elm.appendChild( div );

HTML

<div id="targetID">hello world</div>

Ответ 4

Если у пользователя более старые версии IE (или, может быть, более новые, они не пробовали), innerHTML на td вызовет проблемы. Элементы таблицы в IE доступны только для чтения, tsk tsk tsk.

Ответ 5

Я просто усвоил, почему innerHTML плохой, в этом коде ниже, когда вы устанавливаете innerHTML хром теряет событие onclick jsFiddle

var blah = document.getElementById('blah');
var div = document.createElement('button');
div.style['background-color'] = 'black';
div.style.padding = '20px;';
div.style.innerHTML = 'a';
div.onclick = () => { alert('wtf');};

blah.appendChild(div);

// Uncomment this to make onclick stop working
blah.innerHTML += ' this is the culprit';

<div id="blah">
</div>

Ответ 6

Ответ Майка, вероятно, лучший, но еще одно соображение заключается в том, что вы имеете дело со строками. И конкатенация строк в JavaScript может быть очень медленной, особенно в некоторых старых браузерах. Если вы просто конкатенируете небольшие фрагменты из HTML, то это, вероятно, не заметно, но если у вас есть большая часть страницы, которую вы добавляете что-то неоднократно, вы очень хорошо видите заметную паузу в браузере.

Ответ 7

короткий

Если вы измените innerHTML +=... (обновить содержимое) на innerHTML =... (восстановить содержимое), вы получите очень быстрый код. Похоже, самой медленной частью += является чтение содержимого DOM в виде строки (без преобразования строки в DOM)

Недостаток использования innerHTML заключается в том, что вы теряете старые обработчики событий содержимого - однако вы можете использовать аргументы тега, чтобы пропустить это, например, <div onclick="yourfunc(event)"> что приемлемо в небольших проектах.

Долго

Я провел тесты производительности ЗДЕСЬ на Chrome, Firefox и Safari (2019 мая) (вы можете запустить их на своем компьютере, но наберитесь терпения - это займет ~ 5 минут)

enter image description here

function up() {
  var container = document.createElement('div');
  container.id = 'container';
  container.innerHTML = "<p>Init <span>!!!</span></p>"
  document.body.appendChild(container);
}

function down() {
  container.remove()
}

up();

// innerHTML+=
container.innerHTML += "<p>Just first <span>text</span> here</p>";
container.innerHTML += "<p>Just second <span>text</span> here</p>";
container.innerHTML += "<p>Just third <span>text</span> here</p>";

down();up();

// innerHTML += str
var s='';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML += s;

down();up();

// innerHTML = innerHTML+str
var s=container.innerHTML+'';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML = s;

down();up();

// innerHTML = str
var s="<p>Init <span>!!!</span></p>";
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.innerHTML = s;

down();up();

// insertAdjacentHTML str
var s='';
s += "<p>Just first <span>text</span> here</p>";
s += "<p>Just second <span>text</span> here</p>";
s += "<p>Just third <span>text</span> here</p>";
container.insertAdjacentHTML("beforeend",s);

down();up();

// insertAdjacentHTML
container.insertAdjacentHTML("beforeend","<p>Just first <span>text</span> here</p>");
container.insertAdjacentHTML("beforeend","<p>Just second <span>text</span> here</p>");
container.insertAdjacentHTML("beforeend","<p>Just third <span>text</span> here</p>");

down();up();

// appendChild
var p1 = document.createElement('p');
p1.innerHTML = 'Just first <span>text</span> here';
var p2 = document.createElement('p');
p2.innerHTML = 'Just second <span>text</span> here';
var p3 = document.createElement('p');
p3.innerHTML = 'Just third <span>text</span> here';
container.appendChild(p1);
container.appendChild(p2);
container.appendChild(p3);
b {color: red}
<b>This snippet NOT test anythig - only presents code used in tests</b>