Javascript中this關鍵字詳解

衣舞晨風發表於2015-12-27

原文地址
Quiz

請看下面的程式碼,最後alert出來的是什麼呢?

var name = "Bob";  
var nameObj ={  
    name : "Tom",  
    showName : function(){  
        alert(this.name);  
    },  
    waitShowName : function(){  
        setTimeout(this.showName, 1000);  
    }  
};  

nameObj.waitShowName();

要解決這個問題我們需要了解Javascript的this關鍵字的用法。

this指向哪裡?

一般而言,在Javascript中,this指向函式執行時的當前物件。

In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked.

——jQuery Fundamentals (Chapter 2), by Rebecca Murphey

值得注意,該關鍵字在Javascript中和執行環境,而非宣告環境有關。

The this keyword is relative to the execution context, not the declaration context.

我們舉個例子來說明這個問題:

var someone = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var other = {
    name: "Tom",
    showName: someone.showName
}

other.showName();  //Tom

this關鍵字雖然是在someone.showName中宣告的,但執行的時候是other.showName,所以this指向other.showName函式的當前物件,即other,故最後alert出來的是other.name。

沒有明確的當前物件時

當沒有明確的執行時的當前物件時,this指向全域性物件window。

By default, this refers to the global object.

為什麼說是全域性物件(the global object),因為非瀏覽器情況下(例如:nodejs)中全域性變數並非window物件,而就是叫“全域性變數”(the global object)。不過由於我們這片文章主要討論的是前端開發知識,所以nodejs就被我們忽略了。

例如對於全域性變數引用的函式上我們有:

var name = "Tom";

var Bob = {
    name: "Bob",
    show: function(){
        alert(this.name);
    }
}

var show = Bob.show;
show();  //Tom

你可能也能理解成show是window物件下的方法,所以執行時的當前物件時window。但區域性變數引用的函式上,卻無法這麼解釋:

var name = "window";

var Bob = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var Tom = {
    name: "Tom",
    showName: function(){
        var fun = Bob.showName;
        fun();
    }
};

Tom.showName();  //window

setTimeout、setInterval和匿名函式

文章開頭的問題的答案是Bob。

在瀏覽器中setTimeout、setInterval和匿名函式執行時的當前物件是全域性物件window,這條我們可以看成是上一條的一個特殊情況。

所以在執行this.showName的時候,this指向了window,所以最後顯示了window.name。

瀏覽器中全域性變數可以當成是window物件下的變數,例如全域性變數a,可以用window.a來引用。

我們將程式碼改成匿名函式可能更好理解一些:

var name = "Bob";  
 var nameObj ={  
     name : "Tom",  
     showName : function(){  
         alert(this.name);  
     },  
     waitShowName : function(){  
         !function(__callback){
            __callback();
        }(this.showName);  
     }  
 };  

 nameObj.waitShowName();  //Bob

**在呼叫nameObj.waitShowName時候,我們執行了一個匿名函式,將nameObj.showName作為回撥函式傳進這個匿名函式,然後匿名函式執行時,執行這個回撥函式。由於匿名函式的當前物件是window,所以當在該匿名函式中執行回撥函式時,回撥函式的this指向了window,所以alert出來window.name。

由此看來setTimeout可以看做是一個延遲執行的:

function(__callback){
    __callback();
}

setInterval也如此類比。

但如果我們的確想得到的回答是Tom呢?通過一些技巧,我們能夠得到想要的答案:

var name = "Bob";  
var nameObj ={  
    name : "Tom",  
    showName : function(){  
        alert(this.name);  
    },  
    waitShowName : function(){
        var that = this;
        setTimeout(function(){
            that.showName();
        }, 1000);
    }
}; 

 nameObj.waitShowName();  //Tom

在執行nameObj.waitShowName函式時,我們先對其this賦給變數that(這是為了避免setTimeout中的匿名函式執行時,匿名函式中的this指向window),然後延遲執行匿名函式,執行that.showName,即nameObj.showName,所以alert出正確結果Tom。

eval

對於eval函式,其執行時候似乎沒有指定當前物件,但實際上其this並非指向window,因為該函式執行時的作用域是當前作用域,即等同於在該行將裡面的程式碼填進去。下面的例子說明了這個問題:

var name = "window";

var Bob = {
    name: "Bob",
    showName: function(){
        eval("alert(this.name)");
    }
};

Bob.showName();    //Bob

apply和call

apply和call能夠強制改變函式執行時的當前物件,讓this指向其他物件。因為apply和call較為類似,所以我們以apply為例:

var name = "window";

var someone = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var other = {
    name: "Tom"
};    

someone.showName.apply();    //window
someone.showName.apply(other);    //Tom

apply用於改變函式執行時的當前物件,當無引數時,當前物件為window,有引數時當前物件為該引數。於是這個例子Bob成功偷走了Tom的名字。

new關鍵字

new關鍵字後的建構函式中的this指向用該建構函式構造出來的新物件:

function Person(__name){
    this.name = __name;        //這個this指向用該建構函式構造的新物件,這個例子是Bob物件
}
Person.prototype.show = function(){
    alert(this.name);
}

var Bob = new Person("Bob");
Bob.show();        //Bob

相關文章