在《高程3》中關于setTimeout的this的描述是:超時調用的代碼都是在全局作用域中執(zhí)行的拴签,因此函數(shù)中this的值在非嚴格模式下指向window對象神汹,在嚴格模式下是undefined岔乔,那為什么是這樣呢既绩,我們可以簡單的理解為setTimeout為window對象下的一個方法屋群,本文僅討論非嚴格模式下的情況褂删。依據(jù)高程三的結論开伏,如果真正理解了膀跌,我們可以搞定遇到的百分之90的問題,如:
setTimeout(console.log(this),0)//window
let obj = {
print : function () {
setTimeout(function () {
console.log('setTimeout:'+this);
},0);
}
};
obj.print() //setTimeout: window
function say() {
console.log('setTimeout:'+this);
}let obj = {
print : function () {
setTimeout(say,0);
}
};
obj.print() //setTimeout: window
無論是直接引用固灵、通過對象方法調用還是函數(shù)引用都很容易理解捅伤,有時候,我們會遇到兩個this的情況巫玻,如下丛忆,一個是setTimeout調用環(huán)境中的this,一個是延遲執(zhí)行函數(shù)中的this仍秤,這個時候需要注意區(qū)別蘸际,我們可以理解為,setTimeout中的第一個參數(shù)就是一個單純的函數(shù)的引用而已徒扶,它的指向跟我們一般的函數(shù)調用時一樣取決于被調用時所處的環(huán)境粮彤。
let obj = {
say : function () {
console.log(this); //延遲執(zhí)行函數(shù)中的this
},
print : function () {
setTimeout(this.say,0); //setTimeout調用環(huán)境中的this,指向調用者即obj
}
};
obj.print() //setTimeout: window
我們換種寫法讓上面代碼中setTimeout調用環(huán)境中的this指向window姜骡,此時函數(shù)執(zhí)行就不會有什么效果了
let obj = {
say : function () {
console.log(this); //延遲執(zhí)行函數(shù)中的this
},
print : function () {
setTimeout(this.say,0); //setTimeout調用環(huán)境中的this导坟,指向調用者即obj
}
};
let func = obj.print;
func()
下面再看:
var a = 1;
function func(){
let a = 2;
setTimeout(function(){
console.log(a);
console.log(this.a);
},0)
}
func(); //輸出2 1
var a = 1;
function func(){
// let a = 2;
setTimeout(function(){
console.log(a);
console.log(this.a);
},0)
}
func(); //輸出1 1
可見,在沒有使用this時圈澈,在setTimeout超時調用中變量是跟正常函數(shù)調用時沿著定義時的作用域向上查找的惫周。
那么,當以字符串形式執(zhí)行又是怎么樣呢
var a = 2
function say(a){
console.log(a)
}
function test(){
let a = 1;
setTimeout("say(a)",0)
}
test() //2
var a = 2
function test(){
let a = 1;
function say(a){
console.log(a)
}
setTimeout("say(a)",0)
}
test() //say is not defined
可見康栈,當把say方法移到test內(nèi)部時報錯say is not defined递递,原因是以字符串形式執(zhí)行時javascript內(nèi)部實際上調用了eval(),而eval的執(zhí)行環(huán)境是全局作用域window,全局作用域沒有say方法所以報錯啥么。
將參數(shù)直接以賦值形式傳進去則不會報錯:
var a = 2;
function say(a){
console.log(a)
}
function test(){
let a = 1;
setTimeout("say('hhhh')",0)
}
test() //hhhh
現(xiàn)在看看結合es6的箭頭函數(shù)時this指向是怎么樣的登舞,大家都知道,由于箭頭函數(shù)不綁定this悬荣, 它會捕獲其所在(即定義的位置)上下文的this值菠秒, 作為自己的this值,在setTimeout中情況亦是如此氯迂。
let obj = {
name : "jay",
print : function () {
setTimeout(() => {
console.log(this.name)
},0);
}
};
obj.print() //jay
如何改變setTimeout的this指向
前面的討論其實已經(jīng)有兩種答案了践叠,即利用中間變量引用外面的this和應用箭頭函數(shù)
方法一
let obj = {
name : "jay",
print : function () {
let that = this;
setTimeout(function() {
console.log(that.name)
},0);
}
}; 方法二
let obj = {
name : "jay",
print : function () {
setTimeout(() => {
console.log(this.name)
},0);
}
};
還有一種方法是應用bind方法:
方法三
var name = "window";
function say(){
console.log(this.name);
}
let obj = {
name : "jay",
print : function(){
setTimeout(say.bind(this),0)
}
}
obj.print(); //jay
setTimeout參數(shù)傳遞問題
1.setTimeout(function,milliseconds,param1,param2,...); param1,param2,...是可選項言缤,用于給function提供額外的參數(shù),但是注意禁灼,該特性在IE9及之前的IE不能使用9苄!
function say(name) {
console.log(name)
}s
etTimeout(say,0,'jay')
2.字符串形式傳參:
function say(a,b) {
console.log(a+b)
}
//let name = "jay"
setTimeout( "say(3,4)",3000) //三秒后輸出7
注意事項
盡量避免setTimeout第一個參數(shù)為字符串弄捕,setTimeout允許講一個字符串作為第一個參數(shù)哮独,js內(nèi)部將會調用eval()函數(shù)用來動態(tài)執(zhí)行一段字符串腳本,eval()具有許多不可預見的危險性察藐,eval的效率是非常低的,執(zhí)行一段代碼需要先將字符串轉換為可執(zhí)行代碼舟扎,也就是比平常多了一步分飞,并且可能隱式創(chuàng)建全局變量。
setTimeout遞歸調用時注意記得應用clearTimeout清除睹限,以避免無限遞歸造成內(nèi)存泄漏譬猫。