【javascript 技巧】談談setTimeout的作用域以及this的指向問題


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的用法,以及需要注意的問題,敬請期待。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM