在setTimeout上更改匿名函数的范围会导致奇怪的警告

时间:2009-11-13 11:05:54

标签: javascript scope

这让我感兴趣的纯粹是研究和个人发展。我有一组命名空间的函数/变量。

在1个函数中我需要通过setTimeout调用另一个函数但是将范围保持为'this'。我正在努力解决这个问题,似乎无法在setTimeout运行时绑定它。

var foo = {
    ads: ["foo","bar"],
    timeDelay: 3,
    loadAds: function() {
        var al = this.ads.length;
            if (!al)
                return; // no ads

            for(var i = 0; i < al; i++) {
                setTimeout(function() {
                    this.scrollAd(this.ads[i]);
                }.apply(this), this.timeDelay * 1000);
            }
        },
        scrollAd: function(adBlock) {
            console.log(adBlock);

        }
    };
};

.apply(this)DOES更改范围,因为console.log会输出正确的对象,但它会立即运行该函数,然后在回调保持为空时出现异常/警告:

useless setTimeout call (missing quotes around argument?)

有一种优雅的方式可以做到这一点吗?我知道我能做到

var _this = this;

并在anon回调中引用_this。例如,在mootools中我会使用.bind(this)而不是......

并且不,因为这涉及动画,我不想在字符串周围使用" "因为它需要被评估并且会影响性​​能......

2 个答案:

答案 0 :(得分:9)

for(var i = 0; i < al; i++) {
    setTimeout(function() {
        this.scrollAd(this.ads[i]);
    }.apply(this), this.timeDelay * 1000);
}

apply不会绑定一个函数,它会调用它。因此,您立即执行滚动,然后将其返回值(undefined)传递给setTimeout,这是无效的。

你可能打算在this和循环变量之间使用这样的闭包(必须关闭它,或者每次超时后它都是相同的,循环后的值):

for(var i = 0; i < al; i++) {
    setTimeout(function(that, j) {
        return function() {
            that.scrollAd(that.ads[j]);
        };
    }(this, i), this.timeDelay * 1000);
}

但是您可能更喜欢使用新的ECMAScript第五版功能绑定功能,该功能具有更紧凑的语法:

for (var i= 0; i<al; i++)
    setTimeout(this.scrollAd.bind(this, this.ads[i]), this.timeDelay*1000);

function.bind的实施方案适用于this answer底部没有本地浏览器的浏览器。)

答案 1 :(得分:2)

据我所知,你确实应该使用这样的东西:

var self = this;
setTimeout(function(){self.scrollAd(ad);}, this.timeDelay * 1000);

但如果您非常想使用.apply(),那就这样做:

var self = this;
setTimeout(function(){
    function(){
    }.apply(self);
}, this.timeDelay * 1000);

另请注意,如果在for循环内运行此函数并在计时器中运行的函数内使用i的值,则函数将始终以{{的最后一个值运行1}}(即i)。为了解决这个问题,您需要分别使用i == al的每个值进行闭包。

所以拿你的代码并让它工作它应该是这样的:

i

注意:我没有运行此代码,因此可能包含一些错误。

如果我是你,我会使用来自对象的数据,而不是将其传递给var foo = { ads: ["foo","bar"], timeDelay: 3, loadAds: function() { function runTimed(o, fn, args, time) { setTimeout(function(){ fn.apply(o, args); }, time); } var al = this.ads.length; if (!al) return; // no ads for(var i = 0; i < al; i++) { runTimed(this, this.scrollAd, this.ads[i], this.timeDelay*1000); } }, scrollAd: function(adBlock) { console.log(adBlock); } }; }; scrollAd就足够了。)