【问题标题】:changing the scope of an anonymous function on a setTimeout causes a weird warning在 setTimeout 上更改匿名函数的范围会导致奇怪的警告
【发布时间】:2009-11-13 11:05:54
【问题描述】:

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

在 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) 确实会在 console.log 输出正确的对象时更改范围,但它会立即运行该函数,然后在回调保持为空时出现异常/警告:

useless setTimeout call (missing quotes around argument?)

有没有一种优雅的方式来做到这一点?我知道我能做到

var _this = this;

并在匿名回调中引用_this。例如,在 mootools 中,我会改用 .bind(this)...

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

【问题讨论】:

标签: javascript scope


【解决方案1】:
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);

(在this answer 的底部有一个function.bind 的实现,适用于没有原生的浏览器。)

【讨论】:

  • 是的,很酷。这个 .bind 原型工作正常,正是我所需要的。
【解决方案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 的值,那么您的函数将始终以 i 的最后一个值运行(即 @ 987654327@)。为了解决这个问题,您需要分别使用 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);
        }
    };
};

注意:我没有运行这段代码,所以它可能包含一些错误。

另外,如果我是你,我会使用对象中的数据,而不是将其传递给 scrollAdi 就足够了)。

【讨论】:

  • erm,是的,错过了 i 的迭代问题,恢复为使用 forEach 作为自定义原型迭代器。干杯!
猜你喜欢
  • 2015-04-25
  • 2015-09-20
  • 1970-01-01
  • 2020-07-09
  • 1970-01-01
  • 1970-01-01
  • 2011-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多