Quiz
請看下面的代碼,最后alert出來的是什么呢?
1 var name = "Bob"; 2 var nameObj ={ 3 name : "Tom", 4 showName : function(){ 5 alert(this.name); 6 }, 7 waitShowName : function(){ 8 setTimeout(this.showName, 1000); 9 } 10 }; 11 12 nameObj.waitShowName();
要解決這個問題我們需要了解Javascript的this關鍵字的用法。
this指向哪里?
一般而言,在Javascript中,this指向函數執行時的當前對象。
In JavaScript, as in most object-oriented programming languages,
thisis a special keyword that is used within methods to refer to the object on which a method is being invoked.
值得注意,該關鍵字在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,
thisrefers 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
思考題
1. 請問下面代碼會alert出什么,為什么?
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ alert(this.name); }, waitShowName : function(){ var that = this; setTimeout("that.showName();", 1000); } }; nameObj.waitShowName();
2. 請問下面代碼會alert出什么,為什么?
var fun = new Function("alert(this)"); fun();
3. 下面代碼分別在IE和其他瀏覽器上運行有什么差異,可以用什么方法解決這個差異問題?
IE:
<button id = "box" name = "box">Click Me!</button> <script> var name = "window"; function showName(){ alert(this.name); } document.getElementById("box").attachEvent("onclick", showName); </script>
Others:
<button id = "box" name = "box">Click Me!</button> <script> var name = "window"; function showName(){ alert(this.name); } document.getElementById("box").addEventListener("click", showName, false); </script>
參考文獻
Javascript Closures . Richard Cornford . March 2004
Javascript的this用法 . 阮一峰 . 2010.4.30
