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

setTimeout的用法詳見:http://www.w3school.com.cn/htmldom/met_win_settimeout.asphtml

setTimeout的常見用法是讓某個方法延遲執行setTimeout方法是掛在window對象下的。《JavaScript高級程序設計》第二版中,寫到:「超時調用的代碼都是在全局做用域中執行的,所以函數中this的值在非嚴格模式下指向window對象,在嚴格模式下是undefined」。在這裏,咱們只討論非嚴格模式。dom

setTimeout接受兩個參數,第一個是要執行的代碼或函數,第二個是延遲的時間。函數

1、先說結論:setTimeout中所執行函數中的this,永遠指向window!!注意是要延遲執行的函數中的this哦!!測試

1. 直接使用,代碼1.1:this

setTimeout("alert(this)", 1);   // [object Window]

2. 在一個對象中調用setTimeout試試,代碼1.2:spa

var obj = {
    say : function () {
        setTimeout(function () {
            console.log('setTimeout:'+this);
        },0);
    },
    hello : function () {
        console.log('hello:'+this);
    }
};
obj.say();   //setTimeout:[object Window]

obj.hello();   //hello:[object Object]

3. 換成函數引用再試試吧,代碼1.3:設計

var hello = function () {
    alert( this );
}
var obj = {
    say : function () {
        setTimeout( hello ,1 );
    }
};
obj.say();   //[object Window]

恩,貌似獲得的結論是正確的,setTimeout中的延遲執行函數中的this指向了window。這裏我反覆的強調,是延遲執行函數中的this,是由於,咱們常常會面對兩個this。一個是setTimeout調用環境中的this,一個就是延遲執行函數中的this。這兩個this有時候是不一樣的。有些不放心??再多寫一些代碼測試一下!指針

 2、setTimeout中的兩個this到底指向誰??爲了便於區分,咱們把setTimeout調用環境下的this稱之爲第一個this,把延遲執行函數中的this稱之爲第二個this,並在代碼註釋中標出來,方便區分。先說得出的結論:第一個this的指向是須要根據上下文來肯定的,默認爲window;第二個this就是指向window。而後咱們經過代碼來驗證下。code

 1. 函數做爲方法調用仍是構造函數調用,this是不一樣的。先看代碼,代碼2.1:htm

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。

3、實踐。知道了得出的結論,咱們來閱讀一下比較奇葩的一些代碼,進行驗證。  

首先在一個函數中,調用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();

最後,若是您到看懂了上面的例子,那麼咱們能夠回顧一下得出的一些結論咯:

1、setTimeout中的延遲執行代碼中的this永遠都指向window

2、setTimeout(this.method, time)這種形式中的this,即上文中提到的第一個this,是根據上下文來判斷的,默認爲全局做用域,但不必定老是處於全局下,具體問題具體分析。

3、setTimeout(匿名函數, time)這種形式下,匿名函數中的變量也須要根據上下文來判斷,具體問題具體分析

 

ref:http://www.cnblogs.com/hutaoer/p/3423782.html

相關文章
相關標籤/搜索