最近在模仿swiper鼓搗一個輪播圖插件時诈泼,碰到了setInterval的作用域問題膝迎,輪播的方法寫在一個對象里攘须,但是setInterval執(zhí)行這個方法后摧扇,在方法體內無法訪問這個對象的屬性了圣贸,業(yè)務邏輯代碼如下:
function Swiper(num,loopTime) {
this.num = num; // 輪播起始位置
this.loopTime = loopTime; // 輪播間歇時間
this.timer = null; // 保存setInterval定時器對象Id
this.autoPlay();
}
Swiper.prototype.autoPlay = function() {
clearInterval(this.timer);
// 將循環(huán)方法Loop傳進setInterval執(zhí)行
this.timer = setInterval(this.loop,this.loopTime);
}
Swiper.prototype.loop = function() {
// 循環(huán)主邏輯
console.log('num',this.num);
this.num++;
}
var swiper = new Swiper(0,1000);
以上代碼并沒有按照預期情況(循環(huán)遞增num)來執(zhí)行,執(zhí)行結果為:
分析結果后扛稽,發(fā)現loop方法體內的this并不是指向new出來的swiper對象吁峻,而是window對象,window中沒有num在张,即為undefined用含,自增后,為NaN帮匾。
一耕餐、分析
setInterval(this.loop,this.loopTime)
setInterval是window對象的方法,在setInterval中傳入this.loop方法辟狈,其實是將其作為匿名函數傳入肠缔,this.loop的方法體其實是在window對象上執(zhí)行的,等效為:
setInterval(function() {
// 循環(huán)主邏輯
console.log('num',this.num);
this.num++;
}, this.loopTime);
這樣就一目了然哼转,匿名函數里的this自然是指向window明未,不在指向swiper對象了。
要改變函數作用域壹蔓,一般會用apply/call趟妥,我嘗試用這兩個方法去改變this.loop方法體內的作用域:
setInterval(this.loop.apply(this),this.loopTime);
setInterval(this.loop.call(this),this.loopTime);
但結果都為:
而且只執(zhí)行了一次,為什么只執(zhí)行了一次呢佣蓉?我將Loop方法提出來披摄,放在全局讓window調用:
var num = 0;
function loop() {
console.log(num);
num++;
}
setInterval(loop.apply(window),1000);
setInterval(loop.call(window),1000);
結果都只執(zhí)行了一次亲雪,原來apply和call方法在window的setIntaval只會執(zhí)行一次,原因是apply和call執(zhí)行一次后沒return任何值疚膊,那樣這兩個方法就沒法滿足業(yè)務邏輯了义辕。
二、解決方法
- 閉包
這是最常用最簡單的方法:
Swiper.prototype.autoPlay = function() {
clearInterval(this.timer);
var self = this; // 將this對象保存在self中
this.timer = setInterval(function(){
self.loop(); // 在匿名函數中使用保存后的self對象寓盗,其指向swiper對象
},this.loopTime);
}
運行結果符合預期:num 間歇自增
- ECMAscript 5 中 Function.prototype.bind 方法
這個方法會將函數的this值綁定到傳遞給bind方法的參數上灌砖,并返回一個新的函數實例:
window.num = 1;
var obj = { num: 2};
function alertNum() {
alert(this.num);
}
alertNum(); // 1;
var newAlertNum = alertNum.bind(obj);
newAlertNum(); // 2
通過bind方法來修改setInterval調用:
Swiper.prototype.autoPlay = function() {
clearInterval(this.timer);
// 將loop方法bind到swiper對象上
this.timer = setInterval(this.loop.bind(this),this.loopTime);
}
同樣符合預期:
三、setTimeout
setTimeout也可以用來做循環(huán)調用傀蚌,而且不必擔心像setInterval那樣存在計時器堆疊問題基显,比setInterval有優(yōu)勢(不必像setInterval那樣,調用之前都要用clearInterval清除一下緩存的計時器)善炫,但它同樣屬于window對象撩幽,存在調用時的作用域問題:
var obj = {
num: 0,
loop: function() {
console.log(this.num);
this.num++;
setTimeout(this.loop,1000); // this.num 為undefined
}
};
obj.loop();
延遲1s后調用發(fā)現this.num就為undefined,解決方法與setInterval一樣:使用bind或者閉包
loop: function() {
this.num++;
setTimeout(this.loop.bind(this),1000);
}
loop: function() {
var self = this;
this.num++;
setTimeout(function() {
self.loop();
},1000);
}