Javascript 为什么“element.innerHTML+=”是糟糕的代码?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11515383/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 06:07:14  来源:igfitidea点击:

Why is "element.innerHTML+=" bad code?

javascripthtmlinnerhtmlanti-patterns

提问by ajax333221

I have been told not to append stuff using element.innerHTML += ...like this:

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

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

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

What is wrong with it?, what other alternatives do I have?

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

采纳答案by Mike Christensen

Every time innerHTMLis set, the HTML has to be parsed, a DOM constructed, and inserted into the document. This takes time.

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

For example, if elm.innerHTMLhas thousands of divs, tables, lists, images, etc, then calling .innerHTML += ...is going to cause the parser to re-parse all that stuffover again. This could also break references to already constructed DOM elements and cause other chaos. In reality, all you want to do is append a single new element to the end.

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

It's better to just call appendChild:

最好只调用appendChild

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

This way, the existing contents of elmare not parsed again.

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

NOTE:It's possible that [some] browsers are smart enough to optimize the +=operator and not re-parse the existing contents. I have not researched this.

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

回答by Sheepy

Yes, elm.innerHTML += str;is a very bad idea.
Use elm.insertAdjacentHTML( 'beforeend', str )as the perfect alternative.

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

The typical "browser has to rebuild DOM" answer really doesn't do the question justice:

典型的“浏览器必须重建 DOM”的答案确实没有解决问题:

  1. First the browser need to go through each elements under elm, each of their properties, and all their texts & comments & process nodes, and escape them to build you a string.

  2. Then you have a long string, which you append to. This step is ok.

  3. Third, when you set innerHTML, browser has to remove all the elements, properties, and nodes it just went through.

  4. Then it parse the string, build from all the elements, properties, and nodes it just destroyed, to create a new DOM fragment that is mostly identical.

  5. Finally it attach the new nodes, and the browser has to layout the whole thing. This may be avoidable (see the alternative below), but even if the appended node(s) requires a layout, old nodes would have their layout properties cached instead of re-calculated from fresh.

  6. But it's not done yet! The browser also have to recycle old nodes by scanning alljavascript variables.

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

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

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

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

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

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

Problems:

问题:

  • Some properties may not be reflected by HTML, for example the current value of <input>will be lost and reset to the initial value in the HTML.

  • If you have any event handlers on the old nodes, they will be destroyed and you have to reattach all of them.

  • If your js code is referencing any old nodes, they will not be destroyed but will instead be orphaned. They belong to the document but is no longer in the DOM tree. When your code access them, nothing may happens or it may throws error.

  • Both problems means it is unfriendly with js plugins - the plugins may attach handlers, or keep reference to old nodes and cause memory leak.

  • If you get into the habit of doing DOM manipulation with innerHTML, you may accidentally change properties or do other things you didn't want to.

  • The more nodes you have, the more inefficient this is, the more battery juice for nothing.

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

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

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

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

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

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

In short, it is inefficient, it is error prone, it is simply lazy and uninformed.

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



The best alternative is Element.insertAdjacentHTML, that I haven't seen other answers mention:

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

elm.insertAdjacentHTML( 'beforeend', str )

elm.insertAdjacentHTML( 'beforeend', str )

Almost same code, without innerHTML's problems. No rebuild, no handler lost, no input reset, less memory fragmentation, no bad habit, no manual element creations and assignments.

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

It allows you to inject html string into elements in one line, including properties, and even allows yow to inject composite and multiple elements. Its speed is optimised- in Mozilla's test it is 150times faster.

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

In case someone tell you it is not cross browser, it is so useful that it is HTML5 standardand available on all browsers.

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

Don't ever write elm.innerHTML+=again.

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

回答by ThinkingStiff

The alternative is .createElement(), .textContent, and .appendChild(). Appending with +=is only an issue if you're dealing with a lot of data.

另一种选择是.createElement().textContent,和.appendChild()+=如果您要处理大量数据,附加 with只是一个问题。

Demo:http://jsfiddle.net/ThinkingStiff/v6WgG/

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

Script

脚本

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

HTML

HTML

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

回答by Kamil Kie?czewski

Short

短的

If you change innerHTML += ...(update content) to innerHTML = ...(regenerate content) then you will get very fast code. It looks like the slowest part of +=is READING DOM content as string (not transforming string to DOM)

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

Drawback of using innerHTMLis that you loose old content event handlers - however you can use tag arguments to omit this e.g. <div onclick="yourfunc(event)">which is acceptablein small projects

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

Long

I made performance tests HEREon Chrome, Firefox and Safari (2019 May) (you can run them in your machine but be patient - it takes ~5 min)

我做了性能测试,这里在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();

// 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>



  • For all browsers the innerHTML +=was the slowest solutions.
  • The fastest solution for chrome appendChild- it is ~38% faster than second fast solutions but it is very unhandy. Surprisingly on Firefox appendChildwas slower than innerHTML =.
  • The second fast solutions and similar performance we get for insertAdjacentHTML strand innerHTML = str
  • If we look closer on case innerHTML = innerHTML +strand compare with innerHTML = strit looks like the slowest part of innerHTML +=is READINGDOM content as string (not transforming string to DOM)
  • If you want change DOM tree generate first whole string(with html) and update/regenerate DOM only ONCE
  • mixing appendChildwith innerHTML=is actually slower than pure innerHTML=
  • 对于所有浏览器,这innerHTML +=是最慢的解决方案。
  • chrome 最快的解决方案appendChild- 它比第二个快速解决方案快约 38%,但它非常不方便。令人惊讶的是,在 FirefoxappendChild上比innerHTML =.
  • 第二个快速的解决方案和类似的性能,我们得到insertAdjacentHTML strinnerHTML = str
  • 如果我们仔细观察innerHTML = innerHTML +str并与innerHTML = str它进行比较,看起来最慢的部分innerHTML +=是将DOM 内容作为字符串读取(而不是将字符串转换为 DOM)
  • 如果你想改变 DOM 树生成第一个完整的字符串(带 html)并且只更新/重新生成 DOM一次
  • 混合appendChildinnerHTML=实际上比纯慢innerHTML=

回答by Tango Bravo

If the user has older versions of IE (or maybe newer ones too, haven't tried), innerHTML on a tdwill cause issues. Table elements in IE are read-only, tsk tsk tsk.

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

回答by jjathman

Mike's answer is probably the better one, but another consideration is that you are dealing with strings. And string concatenation in JavaScript can be really slow especially in some older browsers. If you are just concatenating little fragments from HTML then it probably isn't noticeable, but if you have a major part of the page that you are appending something to repeatedly you very well could see a noticeable pause in the browser.

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

回答by Adam Sparks

I just learned the hard way why innerHTML is bad, in this code below when you set innerHTML chrome loses the onclick event jsFiddle

我刚刚学会了为什么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>

回答by DDRRSS

Another reason why "element.innerHTML+=" is bad code is because changing innerHTMLproperty directly has been recently found to be insecure in principle, which is now reflected e.g. in Mozilla's warning about it.

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

Here is a Javascript security/XSS validation-safe/proof practical example of working without using innerHTMLproperty or insertAdjacentHTMLmethod. The content of some htmlElement gets updated-replaced with a new HTML content:

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

However, the parser-generated DOM might be too safe for cases when you need to insert script nodes which might end up appearing in DOM but not executed. In such cases, one might want to use this method:

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

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

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

回答by Léonard Cherouvrier

One way to do it (but not performance tested) : (inspired by DDRRSS response)

一种方法(但未经过性能测试):(受 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));