有人告诉我不要使用element.innerHTML += ...
这样的方式添加内容:
var str = "<div>hello world</div>";
var elm = document.getElementById("targetID");
elm.innerHTML += str; //not a good idea?
这个有什么问题?还有其他的替代方案吗?
有人告诉我不要使用element.innerHTML += ...
这样的方式添加内容:
var str = "<div>hello world</div>";
var elm = document.getElementById("targetID");
elm.innerHTML += str; //not a good idea?
这个有什么问题?还有其他的替代方案吗?
innerHTML
都需要解析 HTML,构建 DOM 并将其插入文档中。这需要时间。elm.innerHTML
有数千个 div、table、列表、图像等,那么调用 .innerHTML += ...
将导致解析器重新解析所有的内容。这可能会破坏对已构造的 DOM 元素的引用并引起其他混乱。实际上,你只想在末尾添加一个新元素。appendChild
。var newElement = document.createElement('div');
newElement.innerHTML = '<div>Hello World!</div>';
elm.appendChild(newElement);
这样,现有的elm
内容不会再次被解析。
注意:某些浏览器可能足够聪明以优化+=
运算符,而不重新解析现有的内容。我没有研究过这一点。
innerHTML
,但我不确定这是否回答了问题。 - laurentinnerHTML
中有 200,000 字节的内容。然后,您附加了一个单独的 <div>Hello</div>
。现在,您需要重新解析 200,000 字节加上单个 DIV 并重新构建整个 DOM。更好的方法是只调用一次 appendChild()
并直接插入到现有的 DOM 中。 - Mike Christensen是的,elm.innerHTML += str;
是一个非常糟糕的想法。
请使用 elm.insertAdjacentHTML('beforeend', str)
作为完美的替代方案。
典型的“浏览器必须重建 DOM”答案并不恰当:
首先,浏览器需要遍历 elm 下的每个元素、它们的各种属性以及所有文本、注释和处理节点,并对它们进行转义以构建字符串。
然后你得到了一个长字符串,将其追加到现有的字符串之后。这一步没有问题。
第三步,当您设置 innerHTML 时,浏览器必须删除刚才经过的所有元素、属性和节点。
然后它解析由所有被摧毁的元素、属性和节点组成的字符串,以创建一个基本相同的新 DOM 片段。
最后,它会附加新节点,浏览器必须重新布局整个内容。这可能是可避免的(见下面的替代方案),但即使追加的节点需要进行布局,旧节点的布局属性也会被缓存,而不是从头重新计算。
但还没完呢!浏览器还必须通过扫描 所有 JavaScript 变量来回收旧节点。
问题:
某些属性可能不会反映在 HTML 中,例如 <input>
的当前值将丢失并重置为 HTML 中的初始值。
如果您在旧节点上有任何事件处理程序,它们将被销毁,您必须重新附加所有事件处理程序。
如果您的 JS 代码引用了旧节点,则它们不会被销毁,而是变成孤儿节点。 它们仍然属于文档,但不再在 DOM 树中。 当您的代码访问它们时,可能什么也不会发生,或者可能会抛出错误。
这两个问题都意味着它与 js 插件不兼容 - 插件可能会附加处理程序或保留对旧节点的引用并导致内存泄漏。
如果您习惯使用 innerHTML 进行 DOM 操作,可能会意外更改属性或执行其他意想不到的操作。
节点越多,这种方法就越低效,而且电池很容易耗尽。
简而言之,这是低效、容易出错的,它只是一种懒惰和无知的行为。
最好的替代方案是Element.insertAdjacentHTML
,我在其他答案中没有看到:
elm.insertAdjacentHTML( 'beforeend', str )
几乎相同的代码,没有innerHTML的问题。 无需重建,无处理程序丢失,无输入重置,更少的内存碎片化,没有坏习惯,也没有手动创建和指定元素。
它允许你在一行中将HTML字符串注入到元素中,包括属性,甚至允许您注入复合和多个元素。 它的速度经过优化 - 在Mozilla的测试中快了150倍。
如果有人告诉你它不是跨浏览器的,请注意它如此有用,以至于它是HTML5的标准并可在所有浏览器上使用。
永远不要再写
elm.innerHTML+=
了。
.innerHTML
的问题。虽然 Element.insertAdjacentHTML
是一种替代方案,但我并不太信服。.innerHTML
有助于替换元素内的所有内容,而 Element.insertAdjacentHTML
则是关于插入而非替换。 - Rahul Desai如果你将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 +=
是最慢的解决方案。appendChild
,比第二个快速解决方案快约38%,但它非常不方便。令人惊讶的是,在Firefox上,appendChild
比 innerHTML =
更慢。insertAdjacentHTML str
和 innerHTML = str
来实现。innerHTML = innerHTML +str
的情况,并将其与 innerHTML = str
进行比较,就会发现 innerHTML +=
最慢的部分是将DOM内容作为字符串读取(而不是将字符串转换为DOM)。appendChild
和 innerHTML=
实际上比纯粹的 innerHTML=
更慢。另一种方法是使用.createElement()
、.textContent
和.appendChild()
。如果你处理大量数据,使用+=
添加可能会出现问题。
演示:http://jsfiddle.net/ThinkingStiff/v6WgG/
var elm = document.getElementById( 'targetID' ),
div = document.createElement( 'div' );
div.textContent = 'goodbye world';
elm.appendChild( div );
<div id="targetID">hello world</div>
如果用户使用较旧版本的IE(或者可能是更新版本,我没有尝试过),在td
上使用innerHTML会导致问题。在IE中,表格元素是只读的,真是太糟糕了。
我刚刚吃了亏,才明白为什么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>
一种方法(但未经过性能测试):(灵感来自DDR RSS的回应)
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));
"element.innerHTML+="是糟糕的代码的另一个原因是,直接更改innerHTML
属性在原则上被发现是不安全的,这现在反映在Mozilla对此的警告中。
这里有一个JavaScript安全/XSS验证安全/证明实际工作的实例,而不使用innerHTML
属性或insertAdjacentHTML
方法。某个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('[一些HTML代码]');
element.innerHTML +=
吗? - laurent<script>
标记都将重新运行。最后,不能保证elm.innerHTML = elm.innerHTML
总是能够产生一个相同的元素DOM结构的副本。 - Tim Down