Почему должен быть y.innerHTML = x.innerHTML; избегать?
Скажем, что у нас есть DIV x
на странице, и мы хотим дублировать ( "copy-paste" ) содержимое этого DIV в другой DIV y
. Мы могли бы сделать это так:
y.innerHTML = x.innerHTML;
или с помощью jQuery:
$(y).html( $(x).html() );
Однако, похоже, этот метод не является хорошей идеей и что его следует избегать.
(1) Почему следует избегать этого метода?
(2) Как это сделать вместо этого?
Update:
Ради этого вопроса допустим, что в DIV x
нет элементов с идентификатором.
(Извините, что я забыл рассмотреть этот случай в моем первоначальном вопросе.)
Вывод:
Я опубликовал свой собственный ответ на этот вопрос ниже (как я и предполагал). Теперь я также планировал принять свой собственный ответ :P
, но ответ на lonesomeday настолько изумительный, что я должен принять его вместо этого.
Ответы
Ответ 1
Этот метод "копирования" HTML-элементов из одного места в другое является результатом неправильного понимания того, что делает браузер. Браузеры не хранят HTML-документ в памяти где-то и многократно изменяют HTML на основе команд из JavaScript.
Когда браузер сначала загружает страницу, он анализирует документ HTML и превращает его в структуру DOM. Это отношение объектов, следующих по стандарту W3C (ну, в основном...). С тех пор исходный HTML полностью избыточен. В браузере все равно, какова была исходная структура HTML; его понимание веб-страницы - это структура DOM, которая была создана из нее. Если ваша разметка HTML неверна/недействительна, она будет каким-то образом исправлена веб-браузером; структура DOM никак не будет содержать недопустимый код.
В принципе, HTML следует рассматривать как способ сериализации структуры DOM, которая должна быть передана через Интернет или храниться в локальном файле.
Поэтому он не должен использоваться для изменения существующей веб-страницы. DOM (Document Object Model) имеет систему для изменения содержимого страницы. Это основано на взаимосвязи узлов, а не на сериализации HTML. Поэтому, когда вы добавляете li
в ul
, у вас есть эти две опции (предполагая, что ul
- элемент списка):
// option 1: innerHTML
ul.innerHTML += '<li>foobar</li>';
// option 2: DOM manipulation
var li = document.createElement('li');
li.appendChild(document.createTextNode('foobar'));
ul.appendChild(li);
Теперь первый вариант выглядит намного проще, но это происходит только потому, что браузер сильно отвлекся на вас: внутренне браузер должен преобразовать дочерние элементы в строку, затем добавить некоторый контент, а затем преобразовать вернемся к структуре DOM. Второй вариант соответствует понятию браузера о том, что происходит.
Второе важное соображение - подумать об ограничениях HTML. Когда вы думаете о веб-странице, не все, что имеет отношение к элементу, может быть сериализовано в HTML. Например, обработчики событий, связанные с x.onclick = function();
или x.addEventListener(...)
, не будут реплицироваться в innerHTML
, поэтому они не будут скопированы. Таким образом, новые элементы в y
не будут иметь прослушиватели событий. Вероятно, это не то, что вы хотите.
Итак, путь вокруг этого заключается в том, чтобы работать с собственными методами DOM:
for (var i = 0; i < x.childNodes.length; i++) {
y.appendChild(x.childNodes[i].cloneNode(true));
}
Чтение документации MDN, вероятно, поможет понять этот способ:
Теперь проблема с этим (как и с вариантом 2 в примере выше) заключается в том, что он очень многословный, гораздо длиннее, чем опция innerHTML
. Это когда вы цените наличие библиотеки JavaScript, которая делает это для вас. Например, в jQuery:
$('#y').html($('#x').clone(true, true).contents());
Это гораздо более явное описание того, что вы хотите. Например, с различными преимуществами производительности и сохранением обработчиков событий, это также помогает понять, что делает ваш код. Это хорошо для вашей души как программиста JavaScript и делает причудливые ошибки значительно менее вероятными!
Ответ 2
- Вы можете дублировать идентификаторы, которые должны быть уникальными.
- jQuery метод clone вызывает,
$(element).clone(true);
будет клонировать данные и прослушиватели событий, но ID все равно будет быть клонированным. Поэтому, чтобы избежать дублирования идентификаторов, не используйте идентификаторы для объектов, которые необходимо клонировать.
Ответ 3
-
Следует избегать, потому что тогда вы теряете любые обработчики, которые могли быть на этом
DOM.
-
Вы можете попытаться обойти это, добавив клоны элементов DOM, а не полностью перезаписывая их.
Ответ 4
Сначала определим задачу, которая должна быть выполнена здесь:
Все дочерние узлы DIV x
должны быть "скопированы" (вместе со всеми его потомками = глубокая копия) и "вставлены" в DIV y
. Если у какого-либо из потомков x
есть один или несколько обработчиков событий, связанных с ним, мы предположительно хотим, чтобы эти обработчики продолжали работать над копиями (после того, как они были помещены внутри y
).
Теперь это не тривиальная задача. К счастью, библиотека jQuery (и все другие популярные библиотеки, как я полагаю) предлагает удобный метод для выполнения этой задачи: .clone()
. Используя этот метод, решение может быть написано так:
$( x ).contents().clone( true ).appendTo( y );
Вышеупомянутое решение является ответом на вопрос (2). Теперь давайте рассмотрим вопрос (1):
Это
y.innerHTML = x.innerHTML;
- это не просто плохая идея - она ужасная. Позвольте мне объяснить...
Вышеуказанное утверждение можно разбить на два этапа.
-
Выражается выражение x.innerHTML
,
-
Возвращаемое значение этого выражения (которое является строкой) присваивается y.innerHTML
.
Узлы, которые мы хотим скопировать (дочерние узлы x
), являются узлами DOM. Это объекты, которые существуют в памяти браузера. При оценке x.innerHTML
браузер сериализует (строит) эти узлы DOM в строку (строку исходного кода HTML).
Теперь, если нам нужна такая строка (например, для ее хранения в базе данных), эта сериализация была бы понятной. Однако нам не нужна такая строка (по крайней мере, не как конечный продукт).
На шаге 2 мы присваиваем эту строку y.innerHTML
. Браузер оценивает это с помощью синтаксического анализа строки, которая приводит к набору узлов DOM, которые затем вставляются в DIV y
(как дочерние узлы).
Итак, подведем итог:
дочерние узлы x
→ строка → строка исходного кода HTML → синтаксический анализ → узлы (копии)
Итак, какая проблема с этим подходом? Ну, узлы DOM могут содержать свойства и функциональные возможности, которые не могут и поэтому не будут сериализованы. Самыми важными такими функциональными возможностями являются обработчики событий, связанные с потомками x
- копии этих элементов не будут связаны с обработчиками событий. Обработчики потерялись в процессе.
Интересную аналогию можно сделать здесь:
Цифровой сигнал → Преобразование D/A → Аналоговый сигнал → Преобразование A/D → Цифровой сигнал
Как вы, наверное, знаете, результирующий цифровой сигнал не является точной копией исходного цифрового сигнала - некоторая информация потерялась в процессе.
Надеюсь, теперь вы поймете, почему следует избегать y.innerHTML = x.innerHTML
.
Ответ 5
Я бы не сделал этого просто потому, что вы просите браузер повторно разгладить разметку HTML, которая уже была проанализирована.
Я бы более склонен использовать родной cloneNode(true)
для дублирования существующих элементов DOM.
var node, i=0;
while( node = x.childNodes[ i++ ] ) {
y.appendChild( node.cloneNode( true ) );
}
Ответ 6
Ну, это действительно зависит. Существует возможность создания повторяющихся элементов с одинаковым идентификатором, что никогда не бывает хорошим.
jQuery также имеет методы, которые могут сделать это для вас.