- 原文:Speeding up JavaScript: Working with the DOM
- 作者:KeeKim Heng, Google 页面工程师
- 翻译:ytzong
当制作 RIA(Rich Internet Application) 时,我们使用 JavaScript 来改变或增加元素。这是靠 DOM (Document Object Model 文档对象模型) 来完成的,这是如何影响 app 的速度呢?
处理 DOM 会使浏览器 reflow (浏览器决定如何显示东东的进程)。直接操作 DOM,改变元素的 CSS 样式,改变浏览器窗口的大小会触发 reflow。访问元素的布局属性(如 offsetHeight 和 offsetWidth) 会触发 reflow。由于每次 reflow 需要时间,所有我们能减少 reflow 越多,app 越快。
处理 DOM 不仅操作页面中存在的元素还包括创建的元素。下面四个部分覆盖了 DOM 操作和 DOM generation,减少浏览器中 reflow 触发的次数。
CSS class 切换 DOM 操作
这个部分我们改变一个元素和它后代的多个样式属性,只触发一次回流。
问题
我们写一个函数来改变一个元素中的所有链接的 className 属性。可以通过简单遍历每个 a 并更新他们的 className 属性。
function selectAnchor(element) {
element.style.fontWeight = 'bold';
element.style.textDecoration = 'none';
element.style.color = '#000';
}
解决方法
我们可以增加一个设置所有样式属性的 class 来解决这个问题。通过给元素增加这个class,现在我们只触发一次浏览器 reflow。同时分离了表现和行为(译注:这个在我之前的《body标签class属性的妙用(Google Reader前端简单分析)》中也有提及)
.selectedAnchor {
font-weight: bold;
text-decoration: none;
color: #000;
}
function selectAnchor(element) {
element.className = 'selectedAnchor';
}
Out-of-the-flow DOM 操作
这个部分我们创建多个元素并把它们插入 DOM 只触发一次 reflow。使用了叫做 DocumentFragment 的东东。我们在 DOM 外创建一个 DocumentFragment (所以是 out-of-the-flow)。之后创建并添加进多个元素。最后,我们移动该 DocumentFragment 中的所有元素到 DOM,这只触发一次 reflow。
问题
让我们写一个改变一个元素中所遇链接的 className 属性的函数。通过简单的遍历每个 a 并更新他们的 href 属性。问题在于,每个 a 会触发一次 reflow。
function updateAllAnchors(element, anchorClass) {
var anchors = element.getElementsByTagName('a');
for (var i = 0, length = anchors.length; i < length; i ++) {
anchors[i].className = anchorClass;
}
}
解决方法
解决这个问题,可以从 DOM 中移除元素,更新所有链接,之后插入回元素到原始位置。为此,我们写一个可复用的函数,不仅从 DOM中移除元素,并且返回一个将会插入回元素至元素位置的函数。
/**
* Remove an element and provide a function that inserts it into its original position
* @param element {Element} The element to be temporarily removed
* @return {Function} A function that inserts the element into its original position
**/
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);
}
};
}
现在我们用这个函数去更新一个元素中的所有链接,这 out-of-the-flow,并且在移除元素时和插入元素时只触发一次 reflow。
function updateAllAnchors(element, anchorClass) {
var insertFunction = removeToInsertLater(element);
var anchors = element.getElementsByTagName('a');
for (var i = 0, length = anchors.length; i < length; i ++) {
anchors[i].className = anchorClass;
}
insertFunction();
}
单个元素的 DOM Generation
这个部分我们创建并添加一个元素至 DOM 中,仅触发一次 reflow。创建元素后,在新元素添加至 DOM 前做好所有的改变。
问题
写一个添加新a 元素至父元素的函数。这个函数让你为该链接提供 class 和 文本。我们可以创建元素,添加进 DOM,之后设置这些属性。这触发3次 reflow。
function addAnchor(parentElement, anchorText, anchorClass) {
var element = document.createElement('a');
parentElement.appendChild(element);
element.innerHTML = anchorText;
element.className = anchorClass;
}
解决方法
最后插入这个子节点至 DOM。这触发一次 reflow。
function addAnchor(parentElement, anchorText, anchorClass) {
var element = document.createElement('a');
element.innerHTML = anchorText;
element.className = anchorClass;
parentElement.appendChild(element);
}
尽管如此,如果我们要添加大量的链接至一个元素时这有一些问题。通过这个方法,每添加一个链接触发一次 reflow。下一部分解决这个问题。
DocumentFragment DOM Generation
这个部分,我们创建多个元素并把它们插入 DOM 触发一次 reflow。使用了一个叫做 DocumentFragment 的东东。我们在 DOM 外创建一个 DocumentFragment (所以是 out-of-the-flow)。之后创建并添加多个元素进来。最后,把所有 DocumentFragment 中的元素移至 DOM 并只触发一次 reflow。
问题
写一个添加10个连接至一个元素的函数。如果只是简单的添加每个新链接至这个元素,会触发10次 reflow。
function addAnchors(element) {
var anchor;
for (var i = 0; i < 10; i ++) {
anchor = document.createElement('a');
anchor.innerHTML = 'test';
element.appendChild(anchor);
}
}
解决方法
为了解决这个问题,我们创建一个 DocumentFragment 并添加每个新链接至此。当我们使用appendChild 添加 DocumentFragment 至元素时,所有 DocumentFragment 的子节点 实际上添加进了这个元素中。这只触发一次 reflow。
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);
}
没有评论:
发表评论