为什么“element.innerHTML + =”代码不好?

时间:2012-07-17 02:50:01

标签: javascript html innerhtml anti-patterns

我被告知不要使用element.innerHTML += ...这样添加内容:

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

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

它有什么问题?我还有其他选择吗?

9 个答案:

答案 0 :(得分:30)

每次设置innerHTML时,都必须解析HTML,构建DOM并插入到文档中。这需要时间。

例如,如果elm.innerHTML有数千个div,表,列表,图像等,那么调用.innerHTML += ...将导致解析器重新解析所有这些东西再一次。这也可能会破坏对已构造的DOM元素的引用并导致其他混乱。实际上,您要做的就是在结尾添加一个新元素。

最好只拨打appendChild

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

这样,elm的现有内容不会再次被解析。

注意: [某些]浏览器可能足够智能以优化+=运算符而不会重新解析现有内容。我没有研究过这个。

答案 1 :(得分:18)

是的,elm.innerHTML += str;是一个非常糟糕的主意。

典型的浏览器必须重建DOM&#34;答案真的不公平:

  1. 首先,浏览器需要遍历elm下的每个元素,每个元素以及所有文本和文本。评论&amp;处理节点,并转义它们以构建一个字符串。

  2. 然后你有一个长字符串,你追加到它。这一步没问题。

  3. 第三,当你设置innerHTML时,浏览器必须删除它刚刚经历的所有元素,属性和节点。

  4. 然后它解析字符串,从它刚刚销毁的所有元素,属性和节点构建,以创建一个大部分相同的新DOM片段。

  5. 最后它附加新节点,浏览器必须布局整个事物。这可能是可以避免的(参见下面的替代方案),但即使附加的节点需要布局,旧节点也会缓存其布局属性,而不是从新鲜重新计算。

  6. 但它还没有完成!浏览器还必须通过扫描所有 javascript变量来回收旧节点。

  7. 问题:

    • HTML可能无法反映某些属性,例如<input>的当前值将丢失并重置为HTML中的初始值。

    • 如果旧节点上有任何事件处理程序,它们将被销毁,您必须重新连接它们。

    • 如果您的js代码引用任何旧节点,它们将不会被销毁,而是会被孤立。 它们属于文档但不再位于DOM树中。 当您的代码访问它们时,不会发生任何事情,或者它可能会引发错误。

    • 这两个问题都意味着它对js插件不友好 - 插件可能会附加处理程序或记住旧节点并导致内存泄漏。

    • 如果您养成使用innerHTML进行DOM操作的习惯,您可能会意外更改属性或执行其他您不想要的操作。

    • 您拥有的节点越多,效率越低,无需任何电池即可。

    简而言之,它效率低下,容易出错,只是懒惰而且不知情。

    最好的选择是Element.insertAdjacentHTML ,我还没有看到其他答案:

    elm.insertAdjacentHTML( 'beforeend', str )

    几乎相同的代码,没有innerHTML的问题。 没有重建,没有处理程序丢失,没有输入重置,更少的内存碎片,没有坏习惯,没有手动元素创建和分配。

    它允许您将html字符串注入一行中的元素,包括属性,甚至允许yow注入复合元素和多个元素。 它的速度是optimised - 在Mozilla的测试中,它的速度 150

    如果有人告诉您它不是跨浏览器,那么它非常有用,它是HTML5 standard并且可以在all browsers中使用。

      

    再也不要写elm.innerHTML+=

答案 2 :(得分:4)

备选方案是.createElement().textContent.appendChild()。如果你正在处理大量数据,那么附加+=只是一个问题。

演示: http://jsfiddle.net/ThinkingStiff/v6WgG/

脚本

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

HTML

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

答案 3 :(得分:1)

如果用户拥有旧版本的IE(或者也可能是较新版本的IE,未尝试过),td上的innerHTML将导致问题。 IE中的表格元素是只读的,tsk tsk tsk。

答案 4 :(得分:1)

我刚刚学会了为什么innerHTML很糟糕,在下面的代码中,当你设置innerHTML chrome时会丢失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>

答案 5 :(得分:0)

迈克的答案可能是更好的答案,但另一个考虑因素是你正在处理字符串。 JavaScript中的字符串连接可能非常慢,特别是在某些旧版浏览器中。如果你只是连接来自HTML的小片段,那么它可能并不明显,但是如果你有一个主要部分的页面,你正在反复追加某些东西,你很可能会在浏览器中看到明显的停顿。

答案 6 :(得分:0)

如果将public(更新内容)更改为innerHTML += ...(重新生成内容),则会得到非常快速的代码。看来innerHTML = ...的最慢部分是作为字符串读取DOM内容(不将字符串转换为DOM)

使用+=的缺点是您放开了旧的内容事件处理程序-但是您可以使用标记参数来忽略此内容,例如innerHTML,在小型项目中是acceptable

我在Chrome,Firefox和Safari上进行了性能测试HERE(2019年5月)(您可以在计算机上运行它们,但要耐心等待-大约需要5分钟)

enter image description here

<div onclick="yourfunc(event)">
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>是最慢的解决方案。
  • 所有浏览器中最快的解决方案是innerHTML +=insertAdjacentHTML str(最快)
  • 如果我们仔细研究案例innerHTML = str并与innerHTML = innerHTML +str进行比较,那么看来innerHTML = str的最慢部分是读取字符串形式的DOM内容(不转换字符串转换为DOM)
  • innerHTML += / createElement是中速解决方案
  • 如果要更改DOM树,请生成第一个完整的字符串(带有html),并且仅更新/重新生成DOM ONCE

答案 7 :(得分:0)

“ element.innerHTML + =“是错误代码的另一个原因是,最近发现原则上直接更改innerHTML属性是不安全的,例如,现在反映出来了。在Mozilla's warning about it中。

这是不使用innerHTML属性或insertAdjacentHTML方法而工作的Javascript安全/ XSS验证安全/证明的实际示例。某些htmlElement的内容被更新,替换为新的HTML内容:

const parser = new DOMParser(),
      tags = parser.parseFromString('[some HTML code]'), `text/html`).body.children, 
      range = document.createRange();
range.selectNodeContents(htmlElement);
range.deleteContents();
for (let i = tags.length; i > 0; i--)
{
     htmlElement.appendChild(tags[0]); 
     // latter elements in HTMLCollection get automatically emptied out with each use of
     // appendChild method, moving later elements to the first position (0), so 'tags' 
     // can not be walked through normally via usual 'for of' loop
}

但是,对于需要插入script nodes which might end up appearing in DOM but not executed的情况,解析器生成的DOM可能太安全了。在这种情况下,可能要使用此方法:

const fragment = document.createRange().createContextualFragment('[some HTML code]');

答案 8 :(得分:0)

一种方法(但未经性能测试):(受DDRRSS响应启发)

    const parser = new DOMParser();
    const parsedBody = parser.parseFromString(str, 'text/html').body;
    for(let i = 0; i <= parsedBody.childNodes.length; i++){
        const tag = parsedBody.childNodes[i];
        if(!tag) continue;

        if(tag instanceof Text){
            codeElement.append(document.createTextNode(tag.textContent));
        } else if(tag instanceof HTMLElement){
            codeElement.appendChild(tag.cloneNode(true));
        }}

    codeElement.appendChild(document.createTextNode(parsedBody.innerText));