2014-04-09 97 views
2

我在JavaScript WebApp中创建了一个简单的观察者模型来处理更复杂的JS对象模型(无DOM事件)上的事件侦听器。可以注册事件监听器函数,然后将其存储在数组中。通过从模型的更广泛应用中调用成员函数,可以执行事件侦听器。到现在为止还挺好。这里的实施效果很好:在JavaScript中异步调用函数

var ModelObserver = function() { 
    this.locationObserverList = []; 
} 

ModelObserver.prototype.emitEvent = function(eventtype, data) { 
    for(var i=0; i < this.locationObserverList.length; i++) { 
     var fns = this.locationObserverList[i]; 
     fns(data); // function is being called 
    } 
}; 

ModelObserver.prototype.registerLocationListener = function(fn) { 
    this.locationObserverList.push(fn); 
}; 

如果在一个小样本html网站中用两个监听器测试它,一切都很好。

现在我想异步调用该函数。我试图改变相应功能的代码如下:

ModelObserver.prototype.emitEvent = function(eventtype, data) { 
    for(var i=0; i < this.locationObserverList.length; i++) { 
     var fns = this.locationObserverList[i]; 
     setTimeout(function() {fns(data);}, 0); 
    } 
}; 

不幸的是我有一个问题在这里:只有第二监听被调用,但现在的两倍。这似乎是与FNS变量冲突,所以我尝试这样做:

ModelObserver.prototype.emitEvent = function(eventtype, data) { 
    var fns = this.locationObserverList; 
    for(var i=0; i < this.locationObserverList.length; i++) { 
     setTimeout(function() {fns[i](data);}, 0); 
    } 
}; 

现在,我得到一个错误:“遗漏的类型错误:对象的特性‘2’[对象数组]是不是一个函数”。

有没有人有一个想法如何让这个工作异步?

回答

1

你给的匿名函数setTimeout有一个对它关闭的变量的持久引用,而不是它们创建时的副本。

你需要让它靠近别的东西。通常情况下,您使用的构建功能setTimeout和args关闭到建设者的函数:

ModelObserver.prototype.emitEvent = function(eventtype, data) { 
    for(var i=0; i < this.locationObserverList.length; i++) { 
     var fns = this.locationObserverList[i]; 
     setTimeout(buildHandler(fns, data), 0); 
     // Or combining those two lines: 
     //setTimeout(buildHandler(this.locationObserverList[i], data), 0); 
    } 
}; 

function buildHandler(func, arg) { 
    return function() { 
     func(arg); 
    }; 
} 

在那里,我们称之为buildHandler具有参考作用,我们希望它获得的参数,buildHandler回报这个函数在被调用时会用该参数调用该函数。我们将返回的函数传递给setTimeout

你也可以做到这一点与ES5的Function#bind,如果你是在ES5环境(或包含适当的垫片,因为这是shimmable):

ModelObserver.prototype.emitEvent = function(eventtype, data) { 
    for(var i=0; i < this.locationObserverList.length; i++) { 
     var fns = this.locationObserverList[i]; 
     setTimeout(fns.bind(undefined, data), 0); 
     // Or combining those two lines: 
     //setTimeout(this.locationObserverList[i].bind(undefined, data), 0); 
    } 
}; 

跳过一些细节,基本上做什么buildHandler上面呢。

更多关于这(在我的博客):Closures are not complicated


边注:通过调度这些功能将在以后通过setTimeout叫,我不认为你可以依赖他们被称为秩序。也就是说,即使你安排了1,2和3,我也不知道你可以依靠他们被这样调用。 The (newish) spec for this指的是定时器的“列表”,从而建议顺序,所以人们可能会想要以相同的超时时间以特定的顺序注册定时器将使它们按照该顺序执行。但我没有(略读)看到规范中的任何保证,所以我不想依赖它。 A very quick and dirty test建议我这样做的实现,但它不是我所依赖的。

+0

不要忘记_setTimeout_也接受'的setTimeout(FN,延迟,参数1,参数2,...)在符合标准的浏览器',它可能是值得一提的是,通过使用_setTimout_循环可以在里面没有 - 更长的保证调用顺序。 –

+0

@PaulS .:这是一个相对较新的创新(Mozilla在很久以前就已经做了,其他的只是最近),在野外的重要浏览器(例如IE8,尽管今天是XP的大日子仍然会持续一段时间来吧,拥有6-21%的全球份额取决于你问的对象)。 –

+0

@PaulS .: *“......你不能再保证调用的顺序......”*好点。尽管这一领域的[新]规范](http://www.w3.org/TR/html5/webappapis.html#timers)将其称为定时器的“列表”,暗示着定单,所以在理论计时器中注册按照给定的顺序具有相同的超时值应该按顺序发生,在规范中我没有看到任何要求。 [看起来会发生什么](http://jsbin.com/zavavope/1),但我不想依赖它。 :-) –

0
ModelObserver.prototype.emitEvent = function(eventtype, data) { 
    var fns = this.locationObserverList; 
    for(var i=0; i < this.locationObserverList.length; i++) { 
     (function(j){ 
      setTimeout(function() {fns[i](data);}, 0); 
     }(i)); 
    } 
}; 

试试这个

0

第二次尝试是行不通的。在您的第一个示例尝试 -

setTimeout(function(){this.fns(data);},0);