YUI2.8自定义事件的一个小bug
2010-3-7
问题
YUI2.8的自定义事件如果添加了两个相同的侦听函数,在移除这些侦听函数时会有错误:
var testEvent = new YAHOO.util.CustomEvent("testEvent"); testEvent.subscribe(eventHandler); testEvent.subscribe(eventHandler); testEvent.unsubscribe(eventHandler); function eventHandler(){ alert("testEvent fire"); } testEvent.fire(); //只会显示一个"testEvent fire"
上面看似没问题,注册了两个相同的侦听函数,执行一次unsubscribe移除了一个,于是只剩一个侦听函数,但事实不是这样:
var testEvent = new YAHOO.util.CustomEvent("testEvent"); testEvent.subscribe(eventHandler); testEvent.subscribe(eventHandler); testEvent.subscribe(eventHandler); testEvent.subscribe(eventHandler); testEvent.unsubscribe(eventHandler); function eventHandler(){ alert("testEvent fire"); } testEvent.fire(); //只会显示两个"testEvent fire"
注册四个相同的侦听函数,执行了一次unsubscribe,却只剩下两个侦听函数有效。
原因
看unsubscribe的源码,在移除侦听函数时用了这样一个循环:
for (var i=0, len=this.subscribers.length; i<len; ++i) { var s = this.subscribers[i]; if (s && s.contains(fn, obj)) { this._delete(i); found = true; } } .... _delete: function(index) { var s = this.subscribers[index]; if (s) { delete s.fn; delete s.obj; } this.subscribers.splice(index, 1); }
问题出在,执行this.subscribers.splice(index,1)时数组长度变了,被删除的元素位置会被下一个元素位置顶替,而for循环里的i继续叠加,就会跳过漏检查这个位置的新元素。
修正方法
1.如果想一次unsubscribe移除所有相同的侦听函数,只需把for改成倒序遍历:
for (var i=this.subscribers.length-1; i>-1; i–)
这样在删除过程中数组元素个数和位置变化也不会导致漏检查。
2.如果想让unsubscribe一次只移除一个侦听函数,应该在for里找到符合条件的元素时跳出循环:
for (var i=0, len=this.subscribers.length; i<;len; ++i) { var s = this.subscribers[i]; if (s && s.contains(fn, obj)) { this._delete(i); found = true; break; } }
不知实际应用中会不会有同时注册两个相同的侦听函数的情况,有的话也是非常少的,所以此bug没什么威胁~
倒序遍历可以这么写:
var len = foo.length;
while(len–){}
效率更高。