setTimeout的用法詳見:http://www.w3school.com.cn/htmldom/met_win_settimeout.asp
是的,setTimeout的常見用法是讓某個方法延遲執行。我們知道,setTimeout方法是掛在window對象下的。《JavaScript高級程序設計》第二版中,寫到:“超時調用的代碼都是在全局作用域中執行的,因此函數中this的值在非嚴格模式下指向window對象,在嚴格模式下是undefined”。在這里,我們只討論非嚴格模式。
setTimeout接受兩個參數,第一個是要執行的代碼或函數,第二個是延遲的時間。
一、先說結論:setTimeout中所執行函數中的this,永遠指向window!!注意是要延遲執行的函數中的this哦!!
1. 直接使用,代碼1.1:
setTimeout("alert(this)", 1); // [object Window]
2. 在一個對象中調用setTimeout試試,代碼1.2:
var obj = { say: function() { setTimeout("alert('in obj ' + this)", 0) } } obj.say(); // in obj [object Window]
3. 將執行的代碼換成匿名函數試試,代碼1.3:
var obj = { say: function() { setTimeout(function(){alert(this)}, 0) } } obj.say(); // [object Window]
4. 換成函數引用再試試吧,代碼1.4:
function talk() { alert(this); } var obj = { say: function() { setTimeout(talk, 0) } } obj.say(); // [object Window]
恩,貌似得到的結論是正確的,setTimeout中的延遲執行函數中的this指向了window。這里我反復的強調,是延遲執行函數中的this,是因為,我們經常會面對兩個this。一個是setTimeout調用環境中的this,一個就是延遲執行函數中的this。這兩個this有時候是不同的。有些不放心??再多寫一些代碼測試一下!
二、setTimeout中的兩個this到底指向誰??為了便於區分,我們把setTimeout調用環境下的this稱之為第一個this,把延遲執行函數中的this稱之為第二個this,並在代碼注釋中標出來,方便您區分。先說得出的結論:第一個this的指向是需要根據上下文來確定的,默認為window;第二個this就是指向window。然后我們通過代碼來驗證下。
1. 函數作為方法調用還是構造函數調用,this是不同的。先看代碼,代碼2.1:
function Foo() { this.value = 42; this.method = function() { // this 指向全局對象 alert(this) // 輸出window 第二個this alert(this.value); // 輸出:undefined 第二個this }; setTimeout(this.method, 500); // this指向Foo的實例對象 第一個this } new Foo();
我們new了一個Foo對象,那么this.method中的this指向的是new的對象,否則無法調用method方法。但是進了method方法后,方法中的this又指向了window,因此this.value的值為undefined。
我們在外層添加一段代碼,再看看,代碼2.2:
var value=33; function Foo() { this.value = 42; this.method = function() { // this 指向全局對象 alert(this) // 輸出window 第二個this alert(this.value); // 輸出:33 第二個this }; setTimeout(this.method, 500); // 這里的this指向Foo的實例對象 第一個this } new Foo();
從這里,可以明顯的看到,method方法中的this指向的是window,因為可以輸出外層的value值。那為什么setTimeout中的this指向的是Foo的實例對象呢?
我覺得代碼2.2就等價於下面的代碼,如代碼2.3:
var value=33; function Foo() { this.value = 42; setTimeout(function(){alert(this);alert(this.value)}, 500); // 先后輸出 window 33 這里是第二個this } new Foo();
setTimeout中的第一個參數就是一個單純的函數的引用而已,而函數中的this仍然指向的是window。在setTimeout(this.method, time) 中的this是可以根據上下文而改變的,其最終的目的是要得到一個函數指針。我們再來驗證一下,看代碼2.4:
function method() { alert(this.value); // 輸出 42 第二個this } function Foo() { this.value = 42; setTimeout(this.method, 500); // 這里this指向window 第一個this } Foo();
這次我們將Foo當成方法直接執行,method方法放到外層,即掛在window上面。而this則指向了window,因此可以調用method方法。method方法中的this仍然指向window,而Foo()執行的時候,對window.value進行了賦值(this.value=42),因此輸出了42。
三、實踐。知道了得出的結論,我們來閱讀一下比較奇葩的一些代碼,進行驗證。
首先在一個函數中,調用setTimeout。代碼3.1:
var test = "in the window"; setTimeout(function() {alert('outer ' + test)}, 0); // 輸出 outer in the window ,默認在window的全局作用域下 function f() { var test = 'in the f!'; // 局部變量,window作用域不可訪問 setTimeout('alert("inner " + test)', 0); // 輸出 outer in the window, 雖然在f方法的中調用,但執行代碼(字符串形式的代碼)默認在window全局作用域下,test也指向全局的test } f();
在f方法中,setTimeout中的test的值是外層的test,而不是f作用域中的test。再看代碼3.2:
var test = "in the window"; setTimeout(function() {alert('outer' + test)}, 0); // outer in the window ,沒有問題,在全局下調用,訪問全局中的test function f() { var test = 'in the f!'; setTimeout(function(){alert('inner '+ test)}, 0); // inner in the f! 有問題,不是說好了執行函數中的this指向的是window嗎?那test也應該對應window下 // 的值才對,怎么test的值卻是 f()中的值呢???? } f();
呀。。按照前面的經驗,f中的setTimeout中的test也應該明明應該是指向外層的test才對吧???我們注意到,這個f里面的setTimeout中的第一個參數是一個匿名函數,這是上面兩端代碼最大的不同。而只要是函數就有它的作用域,我們可以將上面的代碼替換成下面的代碼3.3:
var test = "in the window"; setTimeout(function() {alert('outer ' + test)}, 0); // in the window function f() { var test = 'in the f!'; function ff() {alert('inner ' + test)} // 能訪問到f中的test局部變量 setTimeout(ff, 0); // inner in the f! } f();
再看一段更清晰的代碼,3.4:
var value=33; function Foo() { var value = 42; setTimeout(function(){alert(value);alert(this.value)}, 500); // 先后輸出 42 然后輸出33 這里的this是第二個this } new Foo();
可以確定,延遲執行函數中的this的確是指向了window,毫無疑問,上面的所有代碼都可以驗證哈。但是延遲執行函數中的其他變量需要根據上下文來確認。
修改代碼3.4為3.5,去掉匿名函數的調用方式,會更加清晰:
var value=33; function Foo() { var value = 42; function ff() { alert(value); // 42 alert(this.value); // 33 } setTimeout(ff, 500); // 先后輸出 42 33 } Foo(); // 直接執行,跟普通函數沒有區別
因此,如果去掉Foo中的value=42的話,那么value的值等於多少呢?undefined還是外層的33??請看3.5:
var value=33; function Foo() { function ff() { alert(value); // 輸出33 alert(this.value); // 輸出33 this指向window } setTimeout(ff, 500); // 先后輸出 33 33 } Foo();
沒錯,就是外層的33,因為ff可以訪問到window下的value值,就如同setTimeout中的匿名函數一樣。
最后,我們通過對象的方式進行調用,代碼3.6:
var obj = { name: 'hutaoer', say: function() { var self = this; setTimeout(function(){ alert(self); // 輸出 object ,指向obj alert(this); // 第二個this,指向window,我心永恆,從未改變 alert(self.name) // 輸出 hutaoer }, 0) } } obj.say();
最后,如果您到看懂了上面的例子,那么我們可以回顧一下得出的一些結論咯:
一、setTimeout中的延遲執行代碼中的this永遠都指向window
二、setTimeout(this.method, time)這種形式中的this,即上文中提到的第一個this,是根據上下文來判斷的,默認為全局作用域,但不一定總是處於全局下,具體問題具體分析。
三、setTimeout(匿名函數, time)這種形式下,匿名函數中的變量也需要根據上下文來判斷,具體問題具體分析。### 重新編輯start 謝謝一樓@白夜說 同學的回復,在這里匿名函數的使用形成了一個閉包,從而能訪問到外層函數的局部變量。這樣子去理解,我覺得挺好的!只是這種閉包,跟常見的閉包不同,因為函數式放在setTimeout里面。 ### —— 於2013.11.29下午15:50分 重新編輯 end
今天就到這里,上面的結論都是本人自己總結出來。鄙人才疏學淺,難免有誤,若有紕漏、錯誤或欠妥之處,還望大家指出,在下不吝賜教,力圖互相學習,共同進步。
接下來的一篇,還是談setTimeout的用法,以及需要注意的問題,敬請期待。