關於setInterval和setTImeout中的this指向問題
1. 問題描述
前些天在練習寫一個小例子的時候用到了定時器,發現在setInterval和setTimeout中傳入函式時,函式中的this會指向window物件,如下例:
var num = 0; function Obj (){ this.num = 1, this.getNum = function(){ console.log(this.num); }, this.getNumLater = function(){ setTimeout(function(){ console.log(this.num); }, 1000) } } var obj = new Obj; obj.getNum();//1 列印的是obj.num,值為1 obj.getNumLater()//0 列印的是window.num,值為0
2.問題原因
從上述例子中可以看到setTimeout中函式內的this是指向了window物件,這是由於setTimeout(),setInterval()呼叫的程式碼執行在與所在函式完全分離的執行環境上,,可以理解為非同步執行,獲取不到當前物件內容。這會導致這些程式碼中包含的 this 關鍵字會指向 window (或全域性)物件。
3.解決方法
若想要讓setTimeout中的this指向正確的值,可以使用以下三種比較常用的方法來使this指向正確的值:
1.將當前物件的this存為一個變數,定時器內的函式利用閉包來訪問這個變數,如下:
var num = 0; function Obj (){ var that = this; //將this存為一個變數,此時的this指向obj this.num = 1, this.getNum = function(){ console.log(this.num); }, this.getNumLater = function(){ setTimeout(function(){ console.log(that.num); //利用閉包訪問that,that是一個指向obj的指標 }, 1000) } } var obj = new Obj; obj.getNum();//1 列印的是obj.num,值為1 obj.getNumLater()//1 列印的是obj.num,值為1
這種方法是將當前物件的引用放在一個變數裡,定時器內部的函式來訪問到這個變數,自然就可以得到當前的物件。
2.利用bind()方法
var num = 0; function Obj (){ this.num = 1, this.getNum = function(){ console.log(this.num); }, this.getNumLater = function(){ setTimeout(function(){ console.log(this.num); }.bind(this), 1000) //利用bind()將this繫結到這個函式上 } } var obj = new Obj; obj.getNum();//1 列印的為obj.num,值為1 obj.getNumLater()//1 列印的為obj.num,值為1
bind()方法是在Function.prototype上的一個方法,當被繫結函式執行時,bind方法會建立一個新函式,並將第一個引數作為新函式執行時的this。在這個例子中,在呼叫setTimeout中的函式時,bind方法建立了一個新的函式,並將this傳進新的函式,執行的結果也就是正確的了。關於bind方法可參考 MDN bind 3. 箭頭函式
var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(() => {
console.log(this.num);
}, 1000) //箭頭函式中的this總是指向外層呼叫者,也就是Obj
}
}
var obj = new Obj;
obj.getNum();//1 列印的是obj.num,值為1
obj.getNumLater()//1 列印的是obj.num,值為1
ES6中的箭頭函式完全修復了this的指向,this總是指向詞法作用域,也就是外層呼叫者obj,因此利用箭頭函式就可以輕鬆解決這個問題。
以上三種方法都是比較常用的,當然如果使用call或apply方法來代替bind方法,得到的結果也是正確的,但是call方法會在呼叫之後立即執行,那樣也就沒有了延時的效果,定時器也就沒有用了,所以推薦使用上述方法來將this傳進setTimeout和setInterval中。 所以在使用事件監聽中例如click時候,想要獲取當前物件內容,就可以使用上述幾種方式處理