Javascript是一種特殊的語言,既不是編譯式,也不是直譯式,算是一種動態語言(Dynamic Language)。其中有項特性,就是在Javascript中,包含了變數(Variable)、函數(Function)等等,在存取時,並不使用Pointer方式直接指向記憶體,而是以變數名稱、函數名稱字串在一個Hashtable中查詢(Symbol Lookup),找到變數/函數。尋找的過程會有所謂的Scope Chain的概念:

先找區域變數-->找不到時再找全域變數-->再找不到時搜索DOM

由於Symbol Lookup的過程相當耗用Resource,所以在這上面下功夫可以改善效能。幾個設計要領如下:

  1. 在{ }的範圍內使用var varName宣告,可將變數定義成區域變數,會勝過直接宣告varName;因其存取過程會先找區域,找不到再找全域,都找不到時再馬上建立。
  2. 每個”.”都代表一次Symbol Lookup,例如: document.all.objTable.all.objInput.value會觸發5次。所以,”.”愈少愈好!
  3. 把Function或DOM Object的Method設成區域變數也可以改善效能(很玄) 。
  4. 少用eval!! 每次叫用eval都等於載入一個完整的<script> Block。
  5. 避免String反覆相加,可以利用Array.push, Array.join的寫法取代,效能會好很多。

不過,在TechEd的這堂課程中並沒有提供上述改善的效能數字,我有點懷疑,把原本直覺式的寫法改得面目全非,是否真值回票價?? 於是我寫了以下的網頁,做幾個實地測試。程式主要是反覆用迴圈執行某項特定動作10萬到500萬次,並計算執行完成所耗的時間,如此周而復始進行十次以求平均值。

<body>
<table id="objTable"><tr><td colspan="2">
<input id="objInput" value="Darkthread" /><br />
<select id="objList" style="width:200px" size="11">
</select>
</td></tr>
</table>
<script type="text/javascript">
var lst=document.getElementById("objList");
var sum = 0, times = 10;
for (var c=0; c<times; c++) {
    var st = new Date();
    //Core Testing
    var findObj = document.getElementById;
    for (var i=0; i<100000; i++) {
        var x = findObj("objInput").value;
    }
    //================
    var ed = new Date();
    lst.options[lst.options.length]=
        new Option(c + "->" + (ed-st).toString());
    sum += (ed-st);
}
//Get Average Duration
lst.options[lst.options.length]=
    new Option("Avg->" + (sum/times).toString());
//Function for test
function func1(i) {
    return i;
}
</script>
</body>

測試1: Local Variable vs Global Variable 500萬次

X=1 -- > 2211.2 ms
var X=1 --> 2194.1 ms

測試2: DOM 10萬次

document.all.objInput.value = 1 --> 2828.1 ms (換算成500萬次,要141,405ms)
先var localVar = document.all.objInput,迴圈中再myInp.value = 1 --> 971.4ms (換算成500萬次,也是要45,870ms)

測試3: Function 100萬次

迴圈中直接var x = func1(i); --> 2477.6 ms
先var funcPtr = func1; 迴圈中var x = funcPtr(i); --> 2471.6 ms

測試4: DOM物件存取 100萬次

迴圈中跑var x = objInput.value --> 9808.1 ms
迴圈中跑var x = document.all(“objInput”).value --> 18765ms
迴圈中跑var x = document.getElementById(“objInput”).value --> 16692ms
先宣告var findObj = document.getElementById; 迴圈中跑var x = findObj(“objInput”).value --> 25283ms

測試5: 字串相加 10萬次

var s=""; for (var i=0; i<100000; i++) { s += "A"; } var x=s; --> 3325ms
var a=new Array(); for (var i=0; i<100000; i++) { a.push("A"); } var x = a.join(''); --> 900.3 ms

【結論】

  1. Local Variable與Global Variable的效能差異並不明顯。
  2. 用Lolcal Variable取代某個DOM特定元素,速度快很多(ex: var localVar = document.all.objInput,速度可以快三倍),應歸因於"."的使用減少,加速效果相當明顯。
  3. 利用Local Variable指向DOM中的Method Funtion(例如: var findObj = document.getElementById)意外地比直接呼叫還慢(原因不明),加上這種寫法會讓Code變得難懂,看來沒有採用的必要。
  4. Arrary.push()+join()取代直接字串相加,產生了超過三倍的加速效果,如有極大量的字串相加,可以考慮改寫。另外,ASP.NET AJAX MicrosoftAjax.js裡的StringBuilder物件也有同樣效果,如果專案有啟用ASP.NET AJAX,建議可改用它做字串相加。

PS: 如果想自己用以上的Code做測試,記得要先修改Registry,否則IE發現Javascript連續執行大量程式碼時,會出現"A script on this page is causing Internet Explorer to run slowly. If it continues to run, your computer may become unresponsive. Do you want to abort the script?"的警告,原本這是防止Javascript陷入無窮迴圈拖垮IE的保護機制,卻會對我們的測試造成困擾,可以參考這篇KB,將MaxScriptStatements Registry調成0xffffffff。


Comments

# by 許耀彰

您好: 想請教學長,是否有利用老師講到的ajax view及Fiddler來偵錯語法的經驗呢? 我安裝完後,並不知道如何來使用此兩套工具 謝謝

# by 百練

謝謝

# by Jeffrey

我過去沒用過AJAX View,剛才看了一下,它主要的原理是會啟動一個Proxy Server,IE則要手動將Proxy指向127.0.0.1 8888 Port,接著它會在你的Javascript中偷偷加料,最後透過特殊的URL整理出易懂的圖表。http://research.microsoft.com/projects/ajaxview/AjaxViewUsage-1.htm 上有很詳盡的說明,我在此就不多嘮叼了。 相形之下,Fiddler也是Proxy,但它會自動掛進IE,加上有比較直覺的圖形化操作界面,偵察對象也不限於AJAX,只差在AJAX View針對效能面做出了報表,能更快一眼看出問題所在,個人還是覺得Fiddler比AJAX View來得順手好用,但需要要多一點HTTP的知識才能正確解析結果就是了。 事實上,Fiddler可是本站茶包射手專欄的一哥呢! 呵呵。

Post a comment