jQuery-v2.0.3源码浅析03-Callbacks

废话不多说我们今天来看下jQuery的Callbacks函数。
在看Callbacks源码之前,我们先来看看Callbacks的简单使用吧。
Callbacks用来统一管理函数的。

function aaa(){
    console.log(1);
}
function bbb(){
    console.log(2);
}
var cb= $.Callbacks();
cb.add(aaa);
cb.add(bbb);
cb.fire();

以上代码执行完毕之后,在控制台会依次输出1和2。
如果用传统的方式

function aaa(){
    console.log(1);
}
function bbb(){
    console.log(2);
}
aaa();
bbb();

对比之后可以看出最终函数的执行callbacks只需要调用fire就可以依次执行add添加的函数了。
当然这只是callbacks最简单的使用。

我们再来看一个例子

function aaa(){
  console.log(1);
}
(function(){
  function bbb(){
    console.log(2);
  }
})();
aaa();
bbb();

这个时候会发现bbb根本找不到,因为bbb函数定义在子作用域里面
我们再来看看callbacks的写法

var cb= $.Callbacks();
function aaa(){
    console.log(1);
}
cb.add(aaa);
(function(){
    function bbb(){
        console.log(2);
    }
    cb.add(bbb);
})();
cb.fire();

我们发现只要我们把cb定义在全局就可以解决这种作用域问题,是不是很方便。

当然再看源码之前我们可以自己模拟一下这个callbacks函数

function callbacks(){
    var list = [],
        self = {
            add: function(fn){
                list.push(fn);
            },
            fire: function(){
                for(var i = 0; i < list.length; ++i){
                    var item = list[i];
                    item && item();
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = callbacks();
cb.add(a);
cb.add(b);
cb.fire();

我们可以看到控制台依次输出了1和2。
其实Callbacks最基础的实现部分就是这样的。

让我们再看看Callbacks的参数吧,Callbacks可以接受'once'、'memory'、'unique'、'stopOnFalse'这种类型,还可以4种类型随便组合用空格隔开例如'once memory'。

先看once参数吧,从单词意思可以看出来这个参数的意思就是让add的方法只执行一次,例如:

function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = $.Callbacks();
cb.add(a);
cb.add(b);
cb.fire();
cb.fire();
//如果不传参数控制台会输出1、2、1、2 ,如果这样调用
var cb = $.Callbacks(‘once’);
//会发现控制台只会输出一遍1、2

这个时候我们可以尝试修改自己的callbacks函数来实现这个效果

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //转换一下参数方便判断
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        self = {
            add: function(fn){
                list.push(fn);
            },
            fire: function(){
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    item && item();
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = callbacks('once');
cb.add(a);
cb.add(b);
cb.fire();
cb.fire();

然后我们再来看一下比较简单的一个参数'stopOnFalse',stopOnFalse的作用就是当list里面函数执行完毕之后如果返回false就停止执行,例如:

function a(){
    console.log(1);
    return false;
}
function b(){
    console.log(2);
}
var cb = $.Callbacks('stopOnFalse');
cb.add(a);
cb.add(b);
cb.fire();

控制台只会输出1

我们也可以改造一下我们的代码来实现这个效果

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //转换一下参数方便判断
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        self = {
            add: function(fn){
                list.push(fn);
            },
            fire: function(){
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    if(item && item() === false && options.stopOnFalse){
                        break;
                    }
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
    return false;
}
function b(){
    console.log(2);
}
var cb = callbacks('stopOnFalse');
cb.add(a);
cb.add(b);
cb.fire();

接下来我们看看'memory'的用法,memory的作用是当fire方法调用之后,我们再使用add添加新的函数的时候直接执行。

function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = $.Callbacks();
cb.add(a);
cb.fire();
cb.add(b);
//发现控制台只会打印1
//接下来我们来改一下参数
var cb = $.Callbacks('memory');
//这个时候控制台会输出1和2

这个时候按照我们自己的思路来设计的话处理逻辑是不是应该放在add方法中,判断一下当前状态如果已经fire过的话,就直接执行,
我们接着来改造一下我们的代码

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //转换一下参数方便判断
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        fired,
        self = {
            add: function(fn){
                if(fired){
                    fn && fn();
                }else{
                    list.push(fn);
                }
            },
            fire: function(){
                fired = true;
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    if(item && item() === false && options.stopOnFalse){
                        break;
                    }
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
function b(){
    console.log(2);
}
var cb = callbacks('memory');
cb.add(a);
cb.fire();
cb.add(b);

接下来我们再看一下'unique'参数,unique参数的作用就是控制add进来的函数不能有重复的。例如:

function a(){
    console.log(1);
}
var cb = $.Callbacks();
cb.add(a);
cb.add(a);
cb.fire();
//执行完毕之后控制台会输出两个1
//我们来改一下参数
var cb = $.Callbacks('unique');
//控制台只会输出一个1

我们来该找一下自己的callbacks吧

function createOptions(options){
    var object = {};
    var list = options.split(' ') || [];
    for(let i = 0; i < list.length; ++i){
        object[list[i]] = true;
    }
    return object;
}
function callbacks( options ){
    //转换一下参数方便判断
    options = createOptions(options);

    var firingIndex,
        firingLength,
        list = [],
        fired,
        self = {
            add: function(fn){
                if(fired){
                    fn && fn();
                }else{
                    if(options.unique && list.indexOf(fn) > -1){
                        return;
                    }
                    list.push(fn);
                }
            },
            fire: function(){
                fired = true;
                firingLength = list.length;
                if(!options.once || options.once && firingIndex == null) firingIndex = 0;

                for(;list && firingIndex < firingLength; ++firingIndex){
                    var item = list[firingIndex];
                    if(item && item() === false && options.stopOnFalse){
                        break;
                    }
                }
            }
        }
    return self;
}
function a(){
    console.log(1);
}
var cb = callbacks('unique');
cb.add(a);
cb.add(a);
cb.fire();

上面我们对callbacks进行了简单实现,但还有非常多的情况没有判断,我们来看一下jQuery是如何处理的吧。
源码注释版

//缓存options下次如果又传了同样的参数就不需要重新计算一边了,提高性能类似 {'once memory': { 'once': true, 'memory': true }}
var optionsCache = {};
// 将字符串类型的options转换成对象,并且缓存起来
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
}
jQuery.Callbacks = function( options ) {

    //转换options对象,options还可以接收类似 { 'once': true, 'memory': true } 的对象
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    var memory,
        fired,
        firing,
        firingStart,
        firingLength,
        firingIndex,
        list = [],
        stack = !options.once && [],//如果设置了once则stack=false,如果没有设置once则stack=[]可以存储后续操作
        fire = function( data ) {
            memory = options.memory && data;//如果设置了memory则memory = data,否则memory=false
            fired = true;//如果执行过fire函数就把fired设置成true,代表已经fire过了
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            firing = true;//fire过程中记录一下状态
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {//如果函数执行完毕之后返回false并且设置了stopOnFalse,直接跳出循环
                    memory = false; 
                    break;
                }
            }
            firing = false;//fire完毕把状态改回来
            if ( list ) {//如果这个Callbacks没有销毁
                if ( stack ) {//在存在在fire过程中调用fire,在fire完毕之后再执行
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                } else if ( memory ) {//设置了once又设置了memory才会执行将list清空确保不重复执行
                    list = [];
                } else {//如果设置once,没有设置memory 则在一次执行完毕之后 销毁Callbacks
                    self.disable();
                }
            }
        },
        self = {
            add: function() {
                if ( list ) {
                    var start = list.length;//记录list下标,在使用'memory'参数的时候使用
                    (function add( args ) {//循环push函数
                        jQuery.each( args, function( _, arg ) {
                            var type = jQuery.type( arg );
                            if ( type === "function" ) {
                                if ( !options.unique || !self.has( arg ) ) {//如果使用了'unique'并且添加重复函数会直接忽略
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && type !== "string" ) {//add也可以接收一个数组或者类似数组
                                add( arg );
                            }
                        });
                    })( arguments );
                    if ( firing ) {
                        /**
                            考虑到在fire过程中,发现函数里面调用了add方法,这个时候需要更新一下循环长度
                            例如:
                            function a(){
                                console.log(1);
                                funciton b(){
                                    console.log(2);
                                }
                                cb.add(b);
                            }
                        **/
                        firingLength = list.length;
                    } else if ( memory ) {//如果fire过一次并且设置了memory
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            remove: function() {//移除list中的某一函数
                if ( list ) {
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            if ( firing ) {//判断一下是否在fire过程中进行了remove
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },
            has: function( fn ) {//判断数组中是否包含fn
                return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
            },
            empty: function() {//清空list,可以重新添加调用
                list = [];
                firingLength = 0;
                return this;
            },
            disable: function() {//销毁掉这个Callbacks,不可进行其他操作
                list = stack = memory = undefined;
                return this;
            },
            disabled: function() {//判断是否被销毁
                return !list;
            },
            lock: function() {//锁住
                stack = undefined;
                if ( !memory ) {
                    self.disable();
                }
                return this;
            },
            locked: function() {//判断是否锁住了
                return !stack;
            },
            fireWith: function( context, args ) {//处理一下参数,可以设置回调函数的最终context
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( firing ) {//如果fire过程中又调用了fire先存储起来
                        stack.push( args );
                    } else {//调用fire函数,并传入参数
                        fire( args );
                    }
                }
                return this;
            },
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            fired: function() {//判断是否fire过
                return !!fired;
            }
        };

    return self;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容