用戶端動態輸出table資料並展示表格,是web應用中較為常見的工作。對於迴圈列印輸出tr,td本身是一件非常僵硬和暴力的編程辦法,再加上最後繫結元素innerHTML字元流輸出,
系統所消耗的效能代價是非常高昂的,如果我們需要展現的資料非常龐大時,那麼代價也是成倍的。然而這種動態輸出表格的方法是大多數用戶端程式員最常用的方法。那麼基於最常用的方法,
如何才能降低效能成本,改善使用者體驗,快速安全的顯示我們所需要的資料呢?
我認為從根本上調優需要從兩個方面去考慮。
1:server的資料吐出和client的資料解析。這裡涉及的知識點較多,今後再做詳細的說明。但是對於較為複雜的xml的資料格式來說,client的解析應該用xpath定址和dom內建對象相結合的方法,高速定位。
2:DHTML的最佳化。包括dom,css,js的最佳化,也就是MVC(model, view, control)的最佳化。
這裡我們用js動態產生一個table, 構建一個3000行,8列的表格,代碼分多個版本,便於清晰的比較每個版本不同的效能消耗。
vision 0.1 【耗時14694ms】
貌似以下的寫法是沒有任何錯誤,但是確是最暴力,效率最低,效能消耗最大的寫法。對於大量的資料行和列,用for迴圈拼接元素字串,最後innerHTML輸出是不可取的。
3000記錄頁面載入耗時14694毫秒,近15秒。這樣的頁面資料載入是近乎災難的,應該竭力避免。
<html>
<body>
<div id="tableDiv"></div>
<script>
var maxRow =3000;
var maxCol = 8;
var strTbl = "<table border='1'><tbody>";
var strTbody = '';
for(var i = 0; i < maxRow; i++){
strTbody +="<tr>";
for(var j = 0; j < maxCol; j++){
strTbody += "<td>test</td>";
}
strTbody += "</tr>";
}
strTbl = strTbody + "</tbody></table>";
var obj = document.getElementById("tableDiv");
obj.innerHTML = strTbl;
</script>
</body>
</html>
vision 0.2 【耗時3623ms】
這個版本的代碼有非常大的改進,採用DOM技術動態添加元素,說明在需要處理展現大量資料的情況下,運用DOM快速定位並添加繫結元素的方法,效率遠比拼接html元素字串的方法要高許多。
整個頁面載入完成所耗的時間為3623毫秒。3000行的記錄耗時不到4秒,這個版本的代碼結構和編程思路也無可挑剔, 那麼這樣的載入速度是否可以再快些呢?
<html>
<body>
<script>
var _table, _tbody, tr, td, text, maxRow, maxCol;
maxRow = 3000;
maxCol = 8;
_table = document.createElement("table");
_table.border = "1";
_tbody = document.createElement("tbody");
_table.insertBefore(_tbody, null);
document.body.insertBefore(_table, null);
for (var i=0; i<maxRow; i++) {
tr = document.createElement("tr");
for(var j = 0; j<maxCol; j++){
td = document.createElement("td");
text = document.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
}
_tbody.insertBefore(tr, null);
}
</script>
</body>
</html>
vision 0.3 【耗時3320ms】
基於vision 0.2中的代碼,我們可以看到,整個程式碼片段有多處用到了"body","window","document"這樣的對象,在js中類似這樣的對象都為全域對象,對他們的引用操作勢必會消耗效能,
對這些全域變數引用要比簡單通過局部變數引用的代價要昂貴的多。
這裡我們可以將"document.body"的引用緩衝到局部變數中,這樣就完成了一個將全域對象轉換成局部變數的過程。
在代碼中添加:var docBody = document.body;並且將行:document.body.insertBefore(_table, null);替換為:docBody.insertBefore(_table, null);
在代碼中對"document"單個全域對象的引用就達到8×3000=24000次之多,擷取一個document變數比局部變數大約多花費4ms時間, 所以我們下一步把document對象也緩衝起來。
在代碼中添加:var _doc = document;
這樣,我們重新載入頁面,所耗時間為3320毫秒。只比上個版本所耗的時間減少了10%,似乎效能相差不大,但是在我們日常的開發習慣中,將全域的對象緩衝到局部變數中是一個好的開始。
<html>
<body>
<script>
var _table, _tbody, tr, td, text, maxRow, maxCol;
var docBody = document.body;
var _doc = document;
maxRow = 3000;
maxCol = 8;
_table = _doc.createElement("table");
_table.border = "1";
_tbody = _doc.createElement("tbody");
_table.insertBefore(_tbody, null);
docBody.insertBefore(_table, null);
for (var i=0; i<maxRow; i++) {
tr = _doc.createElement("tr");
for(var j = 0; j<maxCol; j++){
td = _doc.createElement("td");
text = _doc.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
}
_tbody.insertBefore(tr, null);
}
</script>
</body>
</html>
vision 0.4 【耗時2779ms】
一個document對象載入速度的最佳化就是在<script>標籤指定defer屬性。首先在這裡簡單介紹一下defer屬性。defer作用是文檔載入完畢了再執行指令碼,這樣迴避免找不到對象的問題,
加上defer等於在頁面完全在入後再執行,相當於window.onload,但應用上比window.onload 更靈活。設定這個屬性僅適合不需要立即運行<SCRIPT>中代碼的情況。
(立即啟動並執行代碼指不在函數體內的--這些代碼將會在指令碼塊載入後立即執行)當defer屬性設定後,IE不會等待載入和轉換這段指令碼。這就也為著頁面載入會快很多。
通常這意味著立即啟動並執行指令碼應該封裝放在一個函數內,並通過document或者body的onload的事件處理。如果你的指令碼是依賴於頁面載入後的使用者動作,如點擊按鈕,或者移動滑鼠到某個地區,會更加有用!
最後請注意兩點:
1、不要在defer型的指令碼程式段中調用document.write命令,因為document.write將產生直接輸出效果。
2、不要在defer型指令碼程式段中包括任何立即執行指令碼要使用的全域變數或者函數。
<html>
<body onload="init()">
<script defer>
function init() {
var _table, _tbody, tr, td, text, maxRow, maxCol;
var docBody = document.body;
var _doc = document;
maxRow = 3000;
maxCol = 8;
_table = _doc.createElement("table");
_table.border = "1";
_tbody = _doc.createElement("tbody");
_table.insertBefore(_tbody, null);
docBody.insertBefore(_table, null);
for (var i=0; i<maxRow; i++) {
tr = _doc.createElement("tr");
for(var j = 0; j<maxCol; j++){
td = _doc.createElement("td");
text = _doc.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
}
_tbody.insertBefore(tr, null);
}
}
</script>
</body>
</html>
vision 0.5 【耗時2650ms】
上一個版本中的頁面載入速度已經縮短到了2779ms。下面我們對代碼進行進一步的最佳化。
我們看到代碼中dom操作,綁定子項目的方法是由下至上包裹,這樣的元素繫結方式會相對較慢。
create <TR>
create <TD>
create TextNode
第一步 insert TextNode into <TD>
第二步 insert <TD> into <TR>
第三步 insert <TR> into TBODY
現在我們將元素的綁定順序顛倒過來,由上至下的包裹繫結元素
create <TR>
create <TD>
create TextNode
第一步 insert <TR> into TBODY
第二步 insert <TD> into <TR>
第三步 insert TextNode into <TD>
<html>
<body onload="init()">
<script defer>
function init() {
var _table, _tbody, tr, td, text, maxRow, maxCol;
var docBody = document.body;
var _doc = document;
maxRow = 3000;
maxCol = 8;
_table = _doc.createElement("table");
_table.border = "1";
_tbody = _doc.createElement("tbody");
_table.insertBefore(_tbody, null);
docBody.insertBefore(_table, null);
for (var i=0; i<maxRow; i++) {
tr = _doc.createElement("tr");
_tbody.insertBefore(tr, null);
for(var j = 0; j<maxCol; j++){
td = _doc.createElement("td");
text = _doc.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
}
}
}
</script>
</body>
</html>
vision 0.6 【耗時2580ms】
這個版本中我們要調優的是修改table的css屬性,使用fixed-table布局(layout)。指定了fixed-table布局後的表格的列寬使用<col>標籤設定。
fixed-table配置樣式將改善table的效能,因為每個儲存格的內容的尺寸不需要進行計算了。這是一個非常實用的效能改善方法,特別是那些有很多列的大型表格。
這個操作也可以通過簡單增加css樣式實現:
<html>
<body onload="init()">
<script defer>
function init() {
var _table, _tbody, tr, td, text, maxRow, maxCol;
var docBody = document.body;
var _doc = document;
maxRow = 3000;
maxCol = 8;
_table = _doc.createElement("table");
_table.border = "1";
_table.style.tableLayout = "fixed";
_tbody = _doc.createElement("tbody");
_table.insertBefore(_tbody, null);
docBody.insertBefore(_table, null);
for (var i=0; i<maxRow; i++) {
tr = _doc.createElement("tr");
_tbody.insertBefore(tr, null);
for(var j = 0; j<maxCol; j++){
td = _doc.createElement("td");
text = _doc.createTextNode("Text");
td.insertBefore(text, null);
tr.insertBefore(td, null);
}
}
}
</script>
</body>
</html>
vision 0.7 【耗時2210ms】
最後的一個版本的調優就是給儲存格賦值的方式。在所有的樣本中,建立了一個TextNode,並添加給TD。而在這個版本中我們將使用innerText代替插入一個text節點,代碼調整為:
td.innerText = "Text";
(注意:innerText只在IE中受支援,屬於IE擴充,相容FireFox可使用innerHTML,但是innerHTML正如文章開頭所說的,效率非常低下,不建議使用)
<html>
<body onload="init()">
<script defer>
function init() {
var _table, _tbody, tr, td, text, maxRow, maxCol;
var docBody = document.body;
var _doc = document;
maxRow = 3000;
maxCol = 8;
_table = _doc.createElement("table");
_table.border = "1";
_table.style.tableLayout = "fixed";
_tbody = _doc.createElement("tbody");
docBody.insertBefore(_table, null);
_table.insertBefore(_tbody, null);
for (var i=0; i<maxRow; i++) {
tr = _doc.createElement("tr");
_tbody.insertBefore(tr, null);
for(var j = 0; j<maxCol; j++){
td = _doc.createElement("td");
td.innerText = "Text";
tr.insertBefore(td, null);
}
}
}
</script>
</body>
</html>
vision 0.8 【耗時672ms】終極最佳化
將字串作為數組對象的方式是目前效率最高,效能最優的方式。
<script>
var t1 = new Date();
</script>
<html>
<head>
<title></title>
<script>
function testTime(){
var t2 = new Date();
alert(t2-t1+"ms");
}
</script>
</head>
<body onload="init();testTime();">
<div id="tableDiv"></div>
<script>
var maxRow =3000;
var maxCol = 8;
var strTbody = ["<table border='1'><tbody>"];
for(var i = 0; i < maxRow; i++){
strTbody.push("<tr>");
for(var j = 0; j < maxCol; j++){
strTbody.push("<td>test</td>");
}
strTbody.push("</tr>");
}
strTbody.push("</tbody></table>");
var obj = document.getElementById("tableDiv");
obj.innerHTML = strTbody.join("");
</script>
</body>
</html>
來源:http://qihonghui1016.blog.163.com/blog/static/116397892007919101332430/