Переваги createElement перед innerHTML?


80

На практиці, які переваги використання createElement перед innerHTML? Я запитую, бо я впевнений, що використання innerHTML є більш ефективним з точки зору продуктивності та читабельності / ремонтопридатності коду, але мої товариші по команді вирішили використовувати createElement як підхід до кодування. Я просто хочу зрозуміти, як createElement може бути більш ефективним.


Відповіді:


77

Є кілька переваг використання createElementзамість модифікації innerHTML(на відміну від того, щоб просто викинути те, що вже є, і замінити), крім безпеки, як уже згадував Пекка:

Зберігає існуючі посилання на елементи DOM при додаванні елементів

Коли ви додаєте (або іншим чином змінюєте) innerHTML , усі вузли DOM всередині цього елемента повинні бути повторно проаналізовані та відтворені. Якщо ви зберегли будь-які посилання на вузли, вони по суті будуть марними, оскільки вони вже не відображаються.

Зберігає обробники подій, приєднані до будь-яких елементів DOM

Це насправді лише окремий випадок (хоча і загальний) останнього. Налаштування innerHTMLне буде автоматично приєднувати обробники подій до нових елементів, які він створює, тому вам доведеться самостійно їх відстежувати та додавати вручну. Делегування подій може усунути цю проблему в деяких випадках.

У деяких випадках може бути простішим / швидшим

Якщо ви робите багато доповнень, ви точно не хочете продовжувати скидання налаштувань, innerHTMLоскільки, хоча це швидше для простих змін, багаторазовий повторний аналіз та створення елементів буде повільнішим. Спосіб обійти це складання HTML у рядку та встановлення innerHTMLодного разу, коли закінчите. Залежно від ситуації, обробка рядків може бути повільнішою, ніж просто створення елементів та їх додавання.

Крім того, код обробки рядків може бути складнішим (особливо якщо ви хочете, щоб він був безпечним).

Ось функція, яку я іноді використовую, робить її зручнішою у використанні createElement.

function isArray(a) {
    return Object.prototype.toString.call(a) === "[object Array]";
}

function make(desc) {
    if (!isArray(desc)) {
        return make.call(this, Array.prototype.slice.call(arguments));
    }

    var name = desc[0];
    var attributes = desc[1];

    var el = document.createElement(name);

    var start = 1;
    if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
        for (var attr in attributes) {
            el[attr] = attributes[attr];
        }
        start = 2;
    }

    for (var i = start; i < desc.length; i++) {
        if (isArray(desc[i])) {
            el.appendChild(make(desc[i]));
        }
        else {
            el.appendChild(document.createTextNode(desc[i]));
        }
    }

    return el;
}

Якщо ви називаєте це так:

make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);

ви отримуєте еквівалент цього HTML:

<p>Here is a <a href="http://www.google.com/">link</a>.</p>

5
Також продемонстровано переваги швидкості створення окремого фрагмента дерева DOM ізольовано, а потім його приєднання до реального DOM один раз, наприкінці.
staticsan

@staticsan, це хороший момент (і ще одне, що makeфункція може трохи полегшити).
Matthew Crumley

Ізольоване створення фрагмента DOM, безумовно, прискорює вставку. Що стосується innerHTML, той самий підхід також може / повинен застосовуватися. Зазвичай я підсуваю свої HTML-рядки до масиву, потім приєднуюсь до елементів за допомогою вбудованої функції приєднання масиву (найшвидший з мого досвіду), а потім додаю це до DOM.
oninea

@Matthew Crumley: IYO, чи не вважаєш ти, що вищезазначені вами переваги (які всі неоднозначні) переважають переваги використання innerHTML у практичному використанні? Щоб бути більш конкретним, скажімо, ви пишете код для динамічного побудови таблиці (досить поширене завдання кодування), чи використовуєте ви для обох підходів фрагменти побудови createElement або innerHTML.
oninea

1
Я взагалі уникаю innerHTMLчерез його недоліки. Для складної розмітки (як побудова таблиці) я зазвичай пишу функції для генерації кожної частини розмітки. Наприклад, я мав би функцію, яка генерує a trз даних для кожного рядка. Тоді у мене може бути інша функція, яка поєднує рядки в таблицю. Кожна функція може бути простою, як виклик makeіз відповідними аргументами. Якщо продуктивність коли-небудь стає проблемою, я можу змінити функції, щоб повернути рядки HTML.
Matthew Crumley

14

Хоча це innerHTMLможе бути швидше, я не згоден з тим, що це краще з точки зору читабельності чи обслуговування. Може бути коротше розмістити все в одному рядку, але коротший код не завжди є більш ремонтопридатним.

Конкатенація рядків просто не масштабується, коли потрібно створювати динамічні елементи DOM, оскільки розкриття та закриття пропозицій та закриття пропозицій стає важким для відстеження. Розглянемо ці приклади:

Отриманий елемент являє собою div з двома внутрішніми інтервалами, вміст яких динамічний. Одне з імен класів (воїн) усередині першого прольоту також є динамічним.

<div>
    <span class="person warrior">John Doe</span>
    <span class="time">30th May, 2010</span>
</div>

Припустимо, що такі змінні вже визначені:

var personClass = 'warrior';
var personName = 'John Doe';
var date = '30th May, 2010';

Використовуючи лише innerHTML і збиваючи все в один рядок, ми отримуємо:

someElement.innerHTML = "<div><span class='person " + personClass + "'>" + personName + "</span><span class='time'>" + date + "</span></div>";

Вищезгаданий безлад можна очистити, використовуючи заміну рядків, щоб уникнути кожного разу відкриття та закриття рядків. Навіть для простих замін тексту я віддаю перевагу використаннюreplace замість конкатенації рядків.

Це проста функція, яка приймає об'єкт ключів та значень заміни та замінює їх у рядку. Він передбачає, що клавіші мають префікс, $щоб позначити їх як особливе значення. Він не виконує жодних екрануючих ситуацій та не обробляє випадків краю, коли це $відображається у значенні заміни тощо.

function replaceAll(string, map) {
    for(key in map) {
        string = string.replace("$" + key, map[key]);
    }
    return string;
}

var string = '<div><span class="person $type">$name</span><span class="time">$date</span></div>';
var html = replaceAll(string, {
    type: personClass,
    name: personName,
    date: date
});
someElement.innerHTML = html;

Це можна поліпшити, відокремлюючи атрибути, текст тощо під час побудови об'єкта, щоб отримати більш програмний контроль над побудовою елемента. Наприклад, за допомогою MooTools ми можемо передавати властивості об'єкта як карту. Це, безумовно, є більш ремонтопридатним, і я б стверджував, що це також читабельніше. jQuery 1.4 використовує подібний синтаксис для передачі карти для ініціалізації об'єктів DOM.

var div = new Element('div');

var person = new Element('span', {
    'class': 'person ' + personClass,
    'text': personName
});

var when =  new Element('span', {
    'class': 'time',
    'text': date
});

div.adopt([person, when]);

Я б не назвав чистий підхід DOM нижче більш читабельним, ніж описаний вище, але він, безумовно, більш ремонтопридатний, оскільки нам не потрібно відстежувати відкриття / закриття котирувань та численних знаків плюса.

var div = document.createElement('div');

var person = document.createElement('span');
person.className = 'person ' + personClass;
person.appendChild(document.createTextNode(personName));

var when = document.createElement('span');
​when.className = 'date​​​​​​';
when.appendChild(document.createTextNode(date));

​div.appendChild(person);
div.appendChild(when);

Найчитабельніша версія, швидше за все, буде результатом використання якогось шаблону JavaScript .

<div id="personTemplate">
    <span class="person <%= type %>"><%= name %></span>
    <span class="time"><%= date %></span>
</div>

var div = $("#personTemplate").create({
    name: personName,
    type: personClass,
    date: date
});

@Anurag: Для вашого першого прикладу про innerHTML я хотів би написати це так. Це трохи менше, ніж я знаю, але особисто мені це читається, навіть структура фрагмента зберігається. Будь-яка думка з цього приводу? array.push ("<div>"); array.push ("<span class = '", person + personClass, "'>", personName, "</span>"); array.push ("<span class = '", time, "'>", дата, </span> "); array.push (" </div> "); someElement.innerHTML = array.join (" " );
oninea

помістіть код всередину зворотних позначок var a = hello, але я можу зрозуміти ваш код. Це виглядає більш читабельним, ніж один зв’язаний рядок.
Анураг

Крім того, робота з відкриттями та закриттями тегів / атрибутів все ще залишається неприємною, навіть якщо розбити на різні етапи. Натомість заміни рядків набагато чистіші та стійкіші до помилок. Я додав приклад вище у своїй відповіді. Це може пропустити мікрооптимізацію побудови рядків, необхідну для IE, але ви не повинні ускладнювати свій код для одного браузера, якщо вам цього не потрібно.
Анураг

1
innerHTMLшвидше? Не згідно з jsperf.com/innerhtml-vs-createelement-and-appendchild
jpillora

Я не думаю, що цей приклад є досить переконливим, щоб щось довести. Вам потрібно створити DOM різного розміру та складності (глибини), використовуючи innerHTML та ручну побудову, і якщо результати узгоджуються, це чітко показує, що innerHTML працює повільніше. Крім того, цей тест є дуже перекошеним, оскільки він змушує механізм візуалізації анулювати весь DOM для тегу body і реконструювати його. Хоча він лише додає новий вузол до DOM в іншому прикладі, і не торкається інших частин тегу тіла.
Анураг

14

Користувач bobince дуже-дуже добре ставить ряд мінусів у своїй критиці jQuery .

... Крім того, ви можете створити div, сказавши $ ('' + message + ''), замість того, щоб мутитися з document.createElement ('div') та текстовими вузлами. Ура! Тільки ... тримайся. Ви не уникли цього HTML, і, напевно, щойно створили отвір безпеки між сценаріями між сайтами, цього разу лише на стороні клієнта. І після того, як ви витратили стільки часу на очищення PHP, щоб використовувати htmlspecialchars і на стороні сервера. Який сором. Ну добре, нікого насправді не хвилює правильність чи безпека, правда?

jQuery не цілком винна в цьому. Зрештою, властивість innerHTML існує вже багато років і вже виявилася популярнішою, ніж DOM. Але бібліотека, безумовно, заохочує такий стиль кодування.

Що стосується продуктивності: InnerHTML, безумовно, буде повільнішим, оскільки його потрібно проаналізувати та внутрішньо перетворити на елементи DOM (можливо, за допомогою createElementметоду).

InnerHTML працює швидше у всіх браузерах відповідно до тесту quirksmode, наданого @Pointy.

Що стосується читання і простоти використання, ви знайдете мене вибір innerHTMLчерез createElementбудь-який день тижня в більшості проектів. Але, як бачите, є багато моментів, за які висловлюєтесь createElement.


1
Uhh @ Pekka, ти справді впевнений, innerHTMLщо ти повільніший? Я знаю, що довгий час це було точно неправдою. Використання innerHTMLнасправді стало популярним у фреймворках саме завдяки значній перевазі продуктивності в деяких браузерах (вгадайте).
Pointy

@Pointy Цікаво. У мене немає ніяких орієнтирів , але здоровий глузд підказує мені , innerHTML повинен бути повільніше: Він повинен бути проаналізований, перевірено, і перетворився в DOM елементи, то , що createElementробить на першому етапі. Якщо ви знаєте якісь орієнтири, які говорять про інше, я з радістю виправлюся.
Pekka

3
Ну, еталон quirksmode трохи старий: quirksmode.org/dom/innerhtml.html
Точний

@Pointy все ще: Ого! Іде повністю проти того, що я міг подумати. Вітаю, оновлю мою відповідь.
Pekka

1
Дякую за розуміння Пекки та Пойнті. Це підсилює мій погляд на те, що innerHTML працює швидше (крім досвіду кодування). Крім того, використання рядків html у масиві забезпечує додатковий приріст продуктивності та читабельність. Я хочу знати, якщо createElement має якісь моменти щодо його використання.
oninea

8

Вам слід використовувати createElement, якщо ви хочете зберегти посилання у своєму коді. InnerHTML іноді може створити помилку, яку важко помітити.

HTML-код:

<p id="parent">sample <span id='test'>text</span> about anything</p>

Код JS:

var test = document.getElementById("test");

test.style.color = "red"; //1 - it works

document.getElementById("parent").innerHTML += "whatever";

test.style.color = "green"; //2 - oooops

1) ви можете змінити колір

2) ти вже не можеш змінити колір або щось інше, тому що у рядку вище ти щось додав до innerHTML, і все відтворено, і ти маєш доступ до чогось, що вже не існує. Для того , щоб змінити його , ви повинні знову getElementById .

Потрібно пам’ятати, що це також впливає на будь-які події. Вам потрібно повторно застосувати події.

InnerHTML чудовий, оскільки його швидше та легше читати, але ви повинні бути обережними та використовувати його з обережністю. Якщо ви знаєте, що робите, з вами все буде в порядку.


0

Інший варіант - це літеральні шаблони (рядки шаблонів).

const container = document.getElementById("container");

const item_value = "some Value";

const item = `<div>${item_value}</div>`

container.innerHTML = item;

Остерігайтеся введення HTML при використанні цього методу.
Шон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.