用匿名函數解決一些scope問題

 Thu, 13 Nov 2008 01:13:56 +0800

這其實是剛剛在Crockford的書中瞄到的,但是很實用。

看看幾個例子吧。

常見到的問題之一,是想要在onclick事件處理函數中使用i變數,但是寫成這樣:

<html>
<body>
<input type="button" value="test1">
<input type="button" value="test2">
<input type="button" value="test3">
<script>
var a = document.getElementsByTagName("input");
var i=0;
for (i=0; i< a.length; i++) {
    a[i].onclick = function() {
        alert(i);
    }
}
</script>
</body>
</html>

結果在執行事件處理函數時,其實for迴圈已經跑完,所以永遠跑出2。這時候用一個匿名函數傳i進去,返回事件處理函數,就可以解決問題:

var a = document.getElementsByTagName("input");
var i=0;
for (i=0; i< a.length; i++) {
    a[i].onclick = function(i) {
        return function(e) {
            alert(i);
        };
    }(i);
}

常見問題之二,是在函數物件實例中把事件處理函數指定給某個node:

function handle() {
    this.abc = "def";
    this.setHandler = function(obj) {
        obj.onclick = function() {
            alert(this.abc);
        };
    }
}
var a = new handle();
a.setHandler(document.getElementsByTagName('input')[0]);

這樣就會出現"undefined"訊息。

可以用匿名函數把this傳給事件處理函數,類似前面的解法:

function handle() {
    this.abc = "def";
    this.setHandler = function(obj) {
        obj.onclick = function(that) {
            return function() {
                alert(that.abc);
            };
        }(this);
    }
}
var a = new handle();
a.setHandler(document.getElementsByTagName('input')[0]);

最後一個常見問題是使用setTimeout,我們常常忘記這個函數是在global context下執行的。像這樣的例子就會出問題:

function handle() {
    this.abc = "def";
    this.delayMsg = function(m) {
        setTimeout(function(){alert(this.abc);}, m);
    };
}
var a = new handle();
a.delayMsg(600);

用匿名函數解決的方法還是類似:

function handle() {
    this.abc = "def";
    this.delayMsg = function(m) {
        setTimeout(function(a){
            return function(e){
                alert(a.abc);
            };
        }(this), m);
    };
}
var a = new handle();
a.delayMsg(600);

所以用匿名函數,curry還有closure,也可以很直覺地解決scope問題呢。