Javascript中的this到底是?

先學JAVA(或任何其他物件導向語言)再開始學Javascript的人,一定會遇到這個問題:
為什麼Javascript裡的this常常找不到東西?
要了解這問題,我們需要先知道一個最基本的觀念:
JAVA是class-based語言,this指向的是該段程式碼所屬的class
Javascript是prototype-based語言,class實際上不存在,this指向的是呼叫該function的主人

再更精確一點的解釋,在JAVA中所有東西都被定義在一個class內,也因此this代表的就是所在的class,就算這method是繼承而來的,只要你是在這個class內呼叫,那麼this就會指到這個class而不是被繼承的class,也因此this是(相較於Javascript)在物件初始化之後就不會變動的東西。而對於Javascript來說,一個function可以跟著定義好的物件,也可以跟著物件的prototype,甚至你可以(在runtime)拿別人的prototype來用、來修改,這樣的彈性間接的導致了this會隨著呼叫的方式而變動。

儘管this不像JAVA是固定的,但其實規則也不複雜,簡單一句話:

this = 呼叫這個function的物件

以下列出可能的情況:

執行方式 範例語法 this等於
Global this; Global object(eg. window)
Global Function foo(); Global object
Object Function myObject.foo(); myObject
Function using call foo.call(myCall, myArg); myCall
Function using apply foo.apply(myApply, [myArgs]); myApply
Constructor Function var newObj = new Foo(); newObj
Evaluation eval(thing_to_eval); 等同eval層級

現在我們知道this是什麼了,但實際運用上呢?其實寫這篇文章部分原因也是因為這是當初最常遇到問題的部分:當我們很高興的使用callback function時,常常會發現裡面的this已經回~不~去~了~

看看範例的程式碼:

var myObj = {
  id: "rettamkrad",
  printId: function() {
    console.log('The id is '+ this.id + ' '+ this.toString());
  }
};
setTimeout(myObj.printId, 100);

myObj.printId的功能很簡單,利用this.id取得物件id並顯示。而這麼簡單的功能,我們透過setTimeout給他延遲個0.1秒執行,結果卻是........this不見了????(或是變成global的id)

在MDN的說明中,setTimeout的callback function裡面的this都會變成global object,儘管我們用call/apply去執行setTimeout也是一樣。在MSN裡面有提供一種解法,叫你重新自訂一個setTimeout來用。不過這裡提供另一種比較常用到的方式:利用closure把this包起來。程式碼很簡單,把之前的setTimeout改成下列這行就好:


setTimeout(function() { myObj.printId() }, 100);

由於此匿名函式會保存myObj,printId也就會正常執行,因此在setTimeout執行時就不會有先前的問題了。


另一種常見的錯誤是這樣:

    myObj = {
        callme: function() {console.log("callme!");},
        init: function() {
            $("#t").click(this.callme);
            });
        }
    }

到JSFiddle看實際執行狀況

點這裡了解一下$().click

這個範例中我們希望可以在div#t被點擊的時候,呼叫myObj中的callme。在程式碼中可以看到callback函式為this.callme,但因為callback時的this已經被設定為click事件發生的DOM元素,所以這樣的寫法當然行不通。

那如果我們用之前的匿名函式方法來作呢:

    myObj = {
        callme: function() {console.log("callme!");},
        init: function() {
            $("#t").click(function(){
                this.callme();
            });
        }
    }

接近了,但因為this是關鍵字,所以在匿名函式中的this一樣指向呼叫這個匿名函式的起源,還是click事件發生的DOM元素。

正確解法:既然this是關鍵字,那麼我們用個變數把this存下來就好啦!

        init: function() {
            var _this = this;
            $("#t").click(function(){_this.callme();});
        }

其它不變,只要我們另外用個變數(在這邊叫做_this)把我們想要丟給callback函式的this存下來,就可以達成想要個目的了。

修改後的JSFiddle

Javascript的this雖然不太容易掌握,但只要知道運行的規則之後其實也沒那麼複雜的。就讓我們快快樂樂的寫code,平平安安的用this吧~

沒有留言:

張貼留言