DOM操作会导致浏览器重解析,引起重排(回流)和重绘,直接影响页面性能
对DOM的操作一般有两种:修改已存在页面上的DOM元素(更改样式),创建插入新的DOM元素
这里提出几个方案来减少DOM操作次数,减少重排和重绘的频率
修改DOM元素的样式
1、给DOM元素直接添加一个新的css类名
适合修改一个DOM元素的样式。给一个DOM元素一次性添加多个样式属性,只触发一次重绘
需求:需要写一个函数来修改一个DOM元素的几个样式,一般实现:逐个添加样式到DOM元素上;但问题是每修改一个样式,就会引起一次重绘。
function addStyle(element) {
element.style.fontWeight = bold;
element.style.textDecoration = none;
element.style.color = #000;
}
解决:创建一个class类名,将要修改的样式放在这个类名里,在需要的时候用JS给DOM元素添加这个类名;可以实现添加多个样式但只触发一次重排(改变元素位置或大小)或重绘(只改变样式不改变位置大小)
.addStyle {
font-weight: bold;
text-decoration: none;
color: #000;
}
function addStyle(element) {
element.className = addStyle;
}
2、在非渲染区修改DOM元素样式
上一个方法是修改一个DOM元素的样式,如果用相同的样式修改多个DOM元素,可以使用这个方法
需求: 写一个函数修改一个指定元素的子元素中所有超链接的样式名。实现:遍历每个超链接修改他们的样式名。问题:每修改一个,会导致一次重绘
function updateAllStyle(element, anchorClass) {
var anchors = element.getElementsByTagName(a);
for (var i = 0, length = anchors.length; i < length; i ++) {
anchors.className = anchorClass; //替换类名
// anchors.setAttribute("class","类名") //替换类名
// anchors.classList.add("类名") //添加类名
}
}
解决:把被修改的元素从DOM中移除,修改所有样式后,再把这个元素插回到原来的位置。
为了完成这个复杂的操作,我们可以先写一个可重用的函数,它不但移除了这个DOM节点,还返回了一个把元素插回到原来的位置的函数 。
function removeToInsertLater(element) {
var parentNode = element.parentNode;
var nextSibling = element.nextSibling;
parentNode.removeChild(element);
return function() {
if (nextSibling) {
parentNode.insertBefore(element, nextSibling);
} else {
parentNode.appendChild(element);
}
};
}
有了上面这个函数,现在我们就可以在一个不需要解析渲染的元素上面修改那些超链接了 。这样只在移除和插入元素的时候各触发一次重解析 。
function updateAllAnchors(element, anchorClass) {
var insertFunction = removeToInsertLater(element); //返回了一个闭包
var anchors = element.getElementsByTagName(a);
for (var i = 0, length = anchors.length; i < length; i ++) {
anchors.className = anchorClass;
}
insertFunction(); //这个闭包引用着removeToInsertLater()函数中的变量对象
}
创建插入新的DOM元素
1、插入元素动作放在最后
适合插入一个元素。在创建元素后,先给元素添加所有的样式,最后再把它插入到DOM里面。这个方法使创建一个元素的过程中只引起一次重排(回流)和重绘 。
需求:实现一个函数,在一个指定的父元素上插入一个新的元素(超链接) ,并且添加新元素的样式 。实现:创建元素,插入到DOM里,设置相应的属性 。缺点是插入元素、每添加一次样式属性都会引发重排重绘
unction addElm(parentElement, anchorText, anchorClass) {
var element = document.createElement(a);
parentElement.appendChild(element); //先插入元素
element.innerHTML = anchorText; //再修改样式
element.className = anchorClass;
}
解决:把插入元素动作放在最后执行,就可以只进行一次重排重绘
var element = document.createElement(a);
element.innerHTML = anchorText; //先修改样式
element.className = anchorClass;
parentElement.appendChild(element); //最后插入元素到DOM中
2、通过文档片段对象(DocumentFragment)创建一组元素
适合创建并插入多个元素并只触发一次重排重绘。
先在 DOM之外创建一个文档片段对象(不需要解析和渲染),然后我们在文档片段对象中创建很多个元素,最后我们把这个文档片段对象中所有的元素一次性放到DOM里面去,只触发一次重排重绘 。
需求:写一个函数,往一个指定的元素上面增加10个超链接 。如果我们简单的直接插入10个超链接到元素上面,就会触发10次重解析 。
function addAnchors(element) {
var anchor;
for (var i = 0; i < 10; i ++) {
anchor = document.createElement(a);
anchor.innerHTML = test;
element.appendChild(anchor);
}
}
解决:先创建一个文档片段对象,把每个新创建的元素都插入到它里面去 。当我们把文档片段对象用appendChild命令插入到指定的节点时,这个文档片段对象的所有子节点就一起被插入到指定的元素里面,而且只需要触发一次重解析 。
function addAnchors(element) {
var anchor, fragment = document.createDocumentFragment();
for (var i = 0; i < 10; i ++) {
anchor = document.createElement(a);
anchor.innerHTML = test;
fragment.appendChild(anchor);
}
element.appendChild(fragment);
}
总结:
修改DOM元素属性:
- 给元素添加修改新的类名
- (el.classList.add("类名"),el.setAttribute("class","类名"),el.className=类名)
- 在非渲染区修改元素属性,也即脱离文档流(移除元素,添加样式,插入元素;
- parentNode.removeChild(el); parentNode.insertBefore(el, nextSibling); parentNode.appendChild(el); )
创建并添加新的DOM元素
- 插入元素放在添加样式后面执行
- document.createElement(el); parentElement.appendChild(element)
- 使用文档片段对象(DocumentFragment)创建一组元素
- fragment = document.createDocumentFragment();fragment.appendChild(el); element.appendChild(fragment);