過去撰寫Javascript事件,我傳統的寫法會宣告一個事件函數,再把它掛到對應的元素上,例如:(以ASP.NET AJAX Client Library為例)

<input type="button" id="btn" value="Click!" />
<script type="text/javascript">
$addHandler($get("btn"), "click", btnClick);
function btnClick(evt) 
{
    alert(evt.target.value);
}
</script>

自從開始使用jQuery,範例看多了,我也開始習慣用Anonymous Function的方式宣告事件函數,例如:

$("#btn").click(function() {
    alert(this.value);
});

這種寫法可以省去想函數名稱的困擾(老是花時間去想btnClick之類沒營養的名字,常覺得在浪費生命),更大的好處是事件函數本體就接在設定事件程式碼的下方,閱讀時不必上下Scroll尋覓(Visual Studio 2008雖有Javascript Intellisense, 畢竟無法像C#/VB.NET一樣用Goto Definition直接跳到函數所在),甚為方便。但我一直有個疑問: 如果$("#tbl td").click(function() {....});一口氣為數百個td加上Click事件,會不會同樣的函數程式碼就複製了上百份? 白白耗用了不必要的記憶體。

疑問在心中壓了很久,總算動手寫了一小段Code來驗證。

程式裡分別用匿名函數及具名函數的方式為一個div重覆掛上click事件3萬次,藉此觀察記憶體的使用狀況。

<html><head>
<title>Memory Usage Test</title>
<style type="text/css">
.clsDiv 
{ cursor: pointer; width: 300px; background-color: blue; color: white; }
</style>
<script type="text/javascript" src="lib/jquery.js"></script>
</head><body>
<script type="text/javascript">
$(function() {
    var count = 30000;
    $("#dv1").click(function() {
        for (var i=0; i < count; i++) 
        {
            $("#dv3").click(function() {
                var s = 
                    "01234567890123456789...用大量文字填充...01234567890123456789" +
                    //重覆多次
                    "01234567890123456789...用大量文字填充...01234567890123456789" +
            });
        }
        $("#dv3").html("dv1 Clicked!");
    });
    
    $("#dv2").click(function() {
        for (var i=0; i < count; i++)
            $("#dv3").click(fatFunc);
        $("#dv3").html("dv2 Clicked!");
    });
});
function fatFunc() {
    var s = 
    "01234567890123456789...用大量文字填充...01234567890123456789" +
    //重覆多次
    "01234567890123456789...用大量文字填充...01234567890123456789" +
}
</script>
<br /><div id="dv1" class="clsDiv">Anonymous Function</div>
<br /><div id="dv2" class="clsDiv">Named Function</div>
<br /><div id="dv3" class="clsDiv">Sheep</div>
</body>

以Task Manager觀察iexplorer.exe佔用記憶體大小當作指標,實驗結果:

測試名稱執行前記憶體用量(KB)執行後記憶體用量(KB)
Anonymous Function54,02483,144
Named Function54,04059,404

【結論】反覆宣告Anonymous Function的確會耗用較多的記憶體,但衡量其方便性及可閱讀性,仍可視需要使用。唯需注意較極端的例子(Client Memory極度匱乏、函數本身很擁腫、重覆宣告次數過高...),應調整成具名函數的寫法,以避免記憶體耗用過度影響效能。


Comments

# by rosbicn

这个不一定是真的占用了内存。 Javascript对于Block的处理不同于VB或者C/C++,一个函数的内部变量使用的内存空间不会在函数结束的时候释放,而是等到Gabage Collect的时候才进行回收。所以你观测到的可能是还没有进行Gabage Collect时候的情况。

# by Jeffrey

To rosbicn, 同意您所提的GC理論。 不過我想匿名函數會因記憶體耗用而影響效能仍值得注意。理由是反覆配置記憶體及GC都要耗用CPU Resource;而這個例子為了省事,測試是"重覆"掛在同一個DIV的onclick事件上,若事件是掛在Table裡上千個<td>上,只要Table仍存在,這些事件函數的記憶體就不能被回收。以上是我的一點補充,請參考。

# by rosbicn

To Jeffrey,你说的绝对正确。如果是挂在Table上千个td上,所有的内存绝对都不能回收。我猜想如果你例子是这么写的话,那测试出来的内存消耗可能会远大于你文章中的结果。的确,用Table中上千个td做例子,你的文章更有说服力。

Post a comment