60

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

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

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

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

4

9 回答 9

59

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

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

最好只打电话appendChild

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

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

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

于 2012-07-17T02:52:23.850 回答
34

是的,elm.innerHTML += str;这是一个非常糟糕的主意。
用作elm.insertAdjacentHTML( 'beforeend', str )完美的替代品。

典型的“浏览器必须重建 DOM”的答案确实不能解决这个问题:

  1. 首先,浏览器需要遍历 elm 下的每个元素,它们的每个属性,以及它们的所有文本、注释和流程节点,并将它们转义以构建一个字符串。

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

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

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

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

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

问题:

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

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

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

  • 这两个问题都意味着它对 js 插件不友好——插件可能会附加处理程序,或者保留对旧节点的引用并导致内存泄漏。

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

  • 您拥有的节点越多,效率就越低,电池电量就越多。

简而言之,它效率低下,容易出错,简直是懒惰和无知。


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

elm.insertAdjacentHTML( 'beforeend', str )

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

它允许您在一行中将 html 字符串注入元素,包括属性,甚至允许您注入复合元素和多个元素。它的速度得到了优化——在 Mozilla 的测试中它快了150倍。

如果有人告诉你它不是跨浏览器,它非常有用,它是 HTML5标准并且在所有浏览器上都可用。

永远不要再写elm.innerHTML+=了。

于 2015-11-30T09:40:23.173 回答
8

短的

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

使用的缺点innerHTML是您会丢失旧的内容事件处理程序 - 但是您可以使用标签参数来省略它,例如<div onclick="yourfunc(event)">这在小型项目中是可以接受的

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

在此处输入图像描述

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();

// appendChild
var p1 = document.createElement("p");
var s1 = document.createElement("span"); 
s1.appendChild( document.createTextNode("text ") );
p1.appendChild( document.createTextNode("Just first ") );
p1.appendChild( s1 );
p1.appendChild( document.createTextNode(" here") );
container.appendChild(p1);

var p2 = document.createElement("p");
var s2 = document.createElement("span"); 
s2.appendChild( document.createTextNode("text ") );
p2.appendChild( document.createTextNode("Just second ") );
p2.appendChild( s2 );
p2.appendChild( document.createTextNode(" here") );
container.appendChild(p2);

var p3 = document.createElement("p");
var s3 = document.createElement("span"); 
s3.appendChild( document.createTextNode("text ") );
p3.appendChild( document.createTextNode("Just third ") );
p3.appendChild( s3 );
p3.appendChild( document.createTextNode(" here") );
container.appendChild(p3);

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 and innerHTML
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 +=是最慢的解决方案。
  • chrome 最快的解决方案appendChild- 它比第二个快速解决方案快约 38%,但它非常不方便。令人惊讶的是,FirefoxappendChildinnerHTML =.
  • 我们获得的第二个快速解决方案和类似的insertAdjacentHTML str性能innerHTML = str
  • 如果我们仔细观察大小写innerHTML = innerHTML +str并与之进行比较,innerHTML = str看起来最慢的部分innerHTML +=是将DOM 内容作为字符串读取(不是将字符串转换为 DOM)
  • 如果您想更改 DOM 树,则生成第一个完整字符串(使用 html)并仅更新/重新生成 DOM一次
  • 混合实际上比纯appendChildinnerHTML=innerHTML=
于 2019-05-03T13:35:43.233 回答
7

另一种选择是.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>
于 2012-07-17T02:58:50.110 回答
2

如果用户使用的是旧版本的 IE(或者也可能是新版本的 IE,还没有尝试过),a 上的 innerHTMLtd会导致问题。IE中的表格元素是只读的,tsk tsk tsk。

于 2012-07-17T02:54:00.240 回答
2

我刚刚学到了为什么 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>
于 2017-04-03T22:51:02.367 回答
1

Mike 的答案可能更好,但另一个考虑因素是您正在处理字符串。JavaScript 中的字符串连接可能非常慢,尤其是在一些较旧的浏览器中。如果您只是连接来自 HTML 的小片段,那么它可能并不明显,但如果您在页面的主要部分重复附加某些内容,您很可能会在浏览器中看到明显的暂停。

于 2012-07-17T02:56:25.800 回答
0

“element.innerHTML+=" 是错误代码的另一个原因是因为innerHTML最近发现直接更改属性在原则上是不安全的,现在这反映在例如Mozilla 的警告中

这是一个不使用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
}

但是,解析器生成的 DOM 对于您需要插入可能最终出现在 DOM 中但未执行的脚本节点的情况可能太安全了。在这种情况下,可能需要使用此方法:

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

于 2020-02-26T10:34:03.283 回答
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));
于 2020-03-24T19:40:40.803 回答