javascript 性能分析:dom 编程

对dom操作在富网页应用中通常是一个性能的瓶颈,主要的3类问题:

一.访问和修改dom元素

    浏览器通常把dom和javascript实现保持相互独立,这样2个独立的部分功能链接就会带来性能损耗。最坏的情况是循环执行该操作。

function innerHTMLLoop() {
for (var count = 0; count < 15000; count++) {
document.getElementById('here').innerHTML += 'a';
}
}
使用局部存储更新后的内容 在循环结束时一次性写入 更佳

function innerHTMLLoop2() {
var content = '';
for (var count = 0; count < 15000; count++) {
content += 'a';
}
document.getElementById('here').innerHTML += content;
}

innerHTML 和DOM方法

function tableInnerHTML() {
var i, h = ['<table border="1" width="100%">'];
h.push('<thead>');
h.push('<tr><th>id<\/th><th>yes?<\/th><th>name<\/th><th>url<\/th><th>action<\/th><\/tr>');
h.push('<\/thead>');
h.push('<tbody>');
for (i = 1; i <= 1000; i++) {
h.push('<tr><td>');
h.push(i);
h.push('<\/td><td>');
h.push('And the answer is... ' + (i % 2 ? 'yes' : 'no'));
h.push('<\/td><td>');
h.push('my name is #' + i);
h.push('<\/td><td>');
h.push('<a href="http://example.org/' + i + '.html">http://example.org/' + i + '.html<\/a>');
h.push('<\/td><td>');
h.push('<ul>');
h.push(' <li><a href="edit.php?id=' + i + '">edit<\/a><\/li>');
h.push(' <li><a href="delete.php?id="' + i + '-id001">delete<\/a><\/li>');
h.push('<\/ul>');
h.push('<\/td>');
h.push('<\/tr>');
}
h.push('<\/tbody>');
h.push('<\/table>');
document.getElementById('here').innerHTML = h.join('');
};
同样:

function tableDOM() {
var i, table, thead, tbody, tr, th, td, a, ul, li;
tbody = document.createElement ('tbody');
for (i = 1; i <= 1000; i++) {
tr = document.createElement ('tr');
td = document.createElement ('td');
td.appendChild(document.createTextNode((i % 2) ? 'yes' : 'no'));
tr.appendChild(td);
td = document.createElement ('td');
td.appendChild(document.createTextNode(i));
tr.appendChild(td);
td = document.createElement ('td');
td.appendChild(document.createTextNode('my name is #' + i));
tr.appendChild(td);
a = document.createElement ('a');

a.setAttribute('href', 'http://example.org/' + i + '.html');
a.appendChild(document.createTextNode('http://example.org/' + i + '.html'));
td = document.createElement ('td');
td.appendChild(a);
tr.appendChild(td);
ul = document.createElement ('ul');
a = document.createElement ('a');
a.setAttribute('href', 'edit.php?id=' + i);
a.appendChild(document.createTextNode('edit'));
li = document.createElement ('li');
li.appendChild(a);
ul.appendChild(li);
a = document.createElement ('a');

a.appendChild(document.createTextNode('delete'));
li = document.createElement ('li');
li.appendChild(a);
ul.appendChild(li);
td = document.createElement ('td');
td.appendChild(ul);
tr.appendChild(td);
tbody.appendChild(tr);
}
tr = document.createElement ('tr');
th = document.createElement ('th');
th.appendChild(document.createTextNode('yes?'));
tr.appendChild(th);
th = document.createElement ('th');
th.appendChild(document.createTextNode('id'));
tr.appendChild(th);
th = document.createElement ('th');

th.appendChild(document.createTextNode('name'));
tr.appendChild(th);
th = document.createElement('th');
th.appendChild(document.createTextNode('url'));
tr.appendChild(th);
th = document.createElement('th');

th.appendChild(document.createTextNode('action'));
tr.appendChild(th);
thead = document.createElement('thead');
thead.appendChild(tr);
table = document.createElement('table');
table.setAttribute('border', 1);

table.setAttribute('width', '100%');
table.appendChild(thead);
table.appendChild(tbody);
document.getElementById('here').appendChild(table);

};

上面2种方法可能因为浏览器不同效果各有千秋。

节点克隆:element.cloneNode() (element是一个存在的节点)代替 document.createElement();

function tableClonedDOM() {
var i, table, thead, tbody, tr, th, td, a, ul, li,
oth = document.createElement('th'),
otd = document.createElement('td'),
otr = document.createElement('tr'),
oa = document.createElement('a'),
oli = document.createElement('li'),
oul = document.createElement('ul');
tbody = document.createElement('tbody');
for (i = 1; i <= 1000; i++) {
tr = otr.cloneNode(false);
td = otd.cloneNode(false);
td.appendChild(document.createTextNode((i % 2) ? 'yes' : 'no'));
tr.appendChild(td);
td = otd.cloneNode(false);
td.appendChild(document.createTextNode(i));

tr.appendChild(td);
td = otd.cloneNode(false);
td.appendChild(document.createTextNode('my name is #' + i));
tr.appendChild(td);
// ... the rest of the loop ...
}
// ... the rest of the table generation ...
}


   HTML 集合
document.getElementsByName()
•document.getElementsByClassName()
•document.getElementsByTagName_r()

document.images

document.links

document.forms

document.forms[0].elements

下面的例子:

var alldivs = document.getElementsByTagName_r('div');
for (var i = 0; i < alldivs.length; i++) {
document.body.appendChild(document.createElement('div'))
}

上面的倍增div 会引起死循环 建议不要使用数组的length属性做循环判断条件 最好的办法是将数组的length属性值缓存到一个变量中

function loopCacheLengthCollection() {
var coll = document.getElementsByTagName_r('div'),
len = coll.length;
for (var count = 0; count < len; count++) {
}
}

同样访问集合元素时也使用局部变量

function collectionGlobal() {
var coll = document.getElementsByTagName_r('div'),
len = coll.length,
name = '';
for (var count = 0; count < len; count++) {
name = document.getElementsByTagName_r('div')[count].nodeName;
name = document.getElementsByTagName_r('div')[count].nodeType;
name = document.getElementsByTagName_r('div')[count].tagName;
}
return name;
};

改版(1)快:

function collectionLocal() {
var coll = document.getElementsByTagName_r('div'),
len = coll.length,
name = '';
for (var count = 0; count < len; count++) {
name = coll[count].nodeName;
name = coll[count].nodeType;
name = coll[count].tagName;
}
return name

};

改版(2)更快:

function collectionNodesLocal() {
var coll = document.getElementsByTagName_r('div'),
len = coll.length,
name = '',
el = null;
for (var count = 0; count < len; count++) {
el = coll[count];
name = el.nodeName;
name = el.nodeType;
name = el.tagName;
}
return name;
};

选择最有效的API

function testNextSibling() {
var el = document.getElementById('mydiv'),
ch = el.firstChild,
name = '';
do {
name = ch.nodeName;
} while (ch = ch.nextSibling);
return name;
};

function testChildNodes() {
var el = document.getElementById('mydiv'),
ch = el.childNodes,
len = ch.length,
name = '';
for (var count = 0; count < len; count++) {
name = ch[count].nodeName;
}
return name;
};

上面的方法在不同的浏览器效果不一样

元素节点:

childNode ,firstChild, nextSibling不区分元素节点和其他类型节点 如注释节点和文本节点 这样节点返回类型进行检查和过滤出非元素节点 这些都是没有必要的。API函数提供了只返回元素节点 最好利用起来。

遍历children 比childNodes快就是这个原理。

在使用API选择器的时候 更精细的控制过程可能造成效率低下,

原生浏览器dom函数querySelectorAll()比使用javascript和dom迭代并缩小元素列表的方法更快。

var elements = document.querySelectorAll('#menu a');


var elements = document.getElementById('menu').getElementsByTagName_r('a');


联合查询优势更大:

var errs = document.querySelectorAll('div.warning, div.notice');


var errs = [],
divs = document.getElementsByTagName_r('div'),
classname = '';
for (var i = 0, len = divs.length; i < len; i++) {
classname = divs[i].className;
if (classname === 'notice' || classname === 'warning') {
errs.push(divs[i]);
}
}

二.修改dom的样式 造成重绘和重新排版

重绘和重新排版对浏览器影响很大 所以在没有必要不要引起重绘和重新排版

1.添加或删除可见的dom元素

2.元素位置的改变

3.元素尺寸的改变 边距 填充 边框宽度 高度

4.内容发改变

5.浏览器窗口的改变大小

查询并刷新渲染树的改变

• offsetTop, offsetLeft, offsetWidth, offsetHeight
• scrollTop, scrollLeft, scrollWidth, scrollHeight
• clientTop, clientLeft, clientWidth, clientHeight
• getComputedStyle() (currentStyle in IE)

布局信息的时候 最好不要使用上面的属性 这些属性任何改变都会重新渲染队列

必要的时候 采用最小重绘和重新排版

var el = document.getElementById('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

改版:

var el = document.getElementById('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

批量修改dom 可以使用下面的方法减少重绘和重新排版

1.从文档流中摘除该元素

    1)隐藏元素 进行修改 然后再显示

    2)使用一个文档片段在已存在dom之外创建一个子树 然后将它拷贝到文档中

    3)将原始元素拷贝到一个脱离文档的节点中,修改副本 然后覆盖原始元素。

2.对其应用多重改变

3.将元素带回文档中

    <ul id="mylist">
<li><a href="http://phpied.com">Stoyan</a></li>
<li><a href="http://julienlecomte.com">Julien</a></li>

</ul>

var data = [
{
"name": "Nicholas",
"url": "http://nczonline.net"
},
{
"name": "Ross",
"url": "http://techfoolery.com"
}
];

function appendDataToElement(appendToElement, data) {
var a, li;
for (var i = 0, max = data.length; i < max; i++) {
a = document.createElement('a');
a.href = data[i].url;
a.appendChild(document.createTextNode(data[i].name));
li = document.createElement('li');
li.appendChild(a);
appendToElement.appendChild(li);

}
};

var ul = document.getElementById('mylist');
appendDataToElement(ul, data);

改版:

var ul = document.getElementById('mylist');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

var fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
document.getElementById('mylist').appendChild(fragment);

var old = document.getElementById('mylist');
var clone = old.cloneNode(true);
appendDataToElement(clone, data);
old.parentNode.replaceChild(clone, old);


myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if (myElement.offsetLeft >= 500) {
stopAnimation();
}

改版:

current++
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current >= 500) {
stopAnimation();
}

三.通过dom事件处理用户响应

事件托管:

事件向上冒泡总能被父元素捕获,常用托管技术后 只需要在一个包装元素上挂接一个句柄,用于处理子元素发生的所有事件(进行事件监听)。捕获  到达目标  冒泡

document.getElementById('menu').onclick = function(e) {
// x-browser target
e = e || window.event;
var target = e.target || e.srcElement;
var pageid, hrefparts;
// only interesed in hrefs
// exit the function on non-link clicks
if (target.nodeName !== 'A') {
return;
}
// figure out page ID from the link
hrefparts = target.href.split('/');
pageid = hrefparts[hrefparts.length - 1];
pageid = pageid.replace('.html', '');

// update the page
ajaxRequest('xhr.php?page=' + id, updatePageContents);
// x-browser prevent default action and cancel bubbling
if (typeof e.preventDefault === 'function') {
e.preventDefault();
e.stopPropagation();
} else {
e.returnValue = false;
e.cancelBubble = true;
}
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值