jQuery Callback API (前传)
发布在读懂jQuery2014年8月20日view:3555
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

这两天小Y开始看jQuery Callback API的代码,无意中发现了一个jQuery的bug,这个bug存在在目前所有的主流版本中,除了最初引进Callback API的1.7版本,从1.8到2.1,都存在。所以,在开始正式开始分析jQuery Callback API的代码前,先来看下这个bug吧。

先来说说这是一个怎么样的bug,看下面的代码:

var a = $.Callbacks();
a.add(function(){console.log('1');});
a.disable();
a.disabled();  //true
a.empty();
a.add(function(){console.log('2');});
a.fire();   // should output nothing, but actually output: '2'

在jQuery的文档中是这么描述disable()方法的:

Disable a callback list from doing anything more

因此,执行disable()方法后,对这个Callback对象的所有操作都应该被禁止了。但是如果在调用disable()之前,没有调用fire(),那么通过调用empty()方法,我们可以重新添加callback函数,然后成功调用fire()方法。之后,虽然add方法可以再次被调用,但是无法再成功调用fire()方法。

我们可以直接从代码入手,看看这个问题的原因:

add: function() {
    if ( list ) {
        // add the callback to callbacks list
        ...
    }
    return this;
},
empty: function() {
    list = [];
    return this;
},
disable: function() {
    list = stack = memory = undefined;
    return this;
},
fireWith: function( context, args ) {
    args = args || [];
    args = [ context, args.slice ? args.slice() : args ];
    if ( list && ( !fired || stack ) ) {
        if ( firing ) {
            stack.push( args );
        } else {
            fire( args );
        }
    }
    return this;
},
fire: function() {
    self.fireWith( this, arguments );
    return this;
}

当我们调用fire()的时候,在内部实际上是在调用fireWith()方法。而fireWith()能被成功调用的前提是list存在,并且fired为false或stack存在。而disable()被调用以后,list、stack与memory都被置为undefined,通过empty()方法,我们只把list重新置为空数组([])了。stack永远是undefined了,如果要fire()方法被成功调用,剩下的可能就只有使fired为false了。那么在没有调用fire()方法之前调用disable(),然后调用empty()方法,可以满足list存在,并且fired为false的条件。

这样,我们就通过empty()方法激活了这个Callback对象的fire()方法了。而且,因为add()方法判断的是list,那么通过调用empty()方法,我们同时激活了这个Callback对象的add()方法了。( ⊙o⊙ )哇

而这两个方法被激活的关键,就是list这个属性,本来已经在disable()方法中被置为undefined了,却又被empty()给还原成空数组了。因此,解决的方法也很简单,在empty()中加一段代码判断list是否存在:

empty: function() {
    if(list) {
        list = [];
    }
    return this;
}

在最新的jQuery 2.1中,应该是这样的写法:

empty: function() {
    if(list) {
        list = [];
        firingLength = 0;
    }
    return this;
}

起初不确定这是否是jQuery的bug,因此在StackOverflow上问了一个问题,结果被验证是一个bug,然后被回答问题的那个人捷足先登,到jQuery的Github仓库拉了一个Pull Request,然后被验证通过了,成了jQuery的贡献者,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,错失良机啊!

小广告:我的博客 YuuuuC.me #^_^#

更多详情,请参见:

StackOverflow

Github

评论
发表评论
5年前
赞了此文章!
6年前

@flow 恩,是啊,便宜他了

6年前

哈哈,看看,老外的动手就是速度啊。

WRITTEN BY
YuC_C
陈胜后裔吗?博客:lovecicy.com
TA的新浪微博
PUBLISHED IN
读懂jQuery

用了这么久jQuery了,想看看到底jQuery怎么写的,为什么这么牛呢。本栏解读的版本为1.8.1,本人(小Y)才疏学浅,如有疏漏、错误之处,还望各位大牛雅正。个人博客:YuuuuC

我的收藏