zoukankan      html  css  js  c++  java
  • 深入jQuery中的Callbacks()

    引入

      初看Callbacks函数很不起眼,但仔细一瞅,发现Callbacks函数是构建jQuery大厦的无比重要的一个基石。jQuery中几乎所有有关异步的操作都会用到Callbacks函数。

    为什么搞了个Callbacks函数?

      1  在 js 开发中,经常会遇到同步和异步这两个概念。

      2  在javascript中神马是同步?神马是异步? 听我讲一个相亲的故事(本故事并不准确,仅供参考):

        1 藤篮是一个漂亮姑娘,明年就要30岁了可现在还没有对象,于是,她的母亲给她报名了两家相亲机构,一家名叫同步相亲机构,另一家叫异步相亲机构。

        2 同步相亲机构:这个机构的负责人很呆板,严格遵从“先来后到”的理念

          负责人首先给藤篮一个小册子,里面记录了很多的男士资料,让藤篮从里面找一个心仪的男士,然后安排藤篮与他见面。

          藤兰很快选中了令狐冲。负责人告诉藤篮:“令狐冲明天和任女士有约,你们只能后天见面了“。藤篮说:“好的,我正好准备准备”。

          结果两天过后,负责人告诉藤篮:因为昨天任女士有事,没能和令狐冲见面,所以我们安排今天任女士与令狐冲见面,你到明天再约吧!

          藤篮很生气:既然昨天任女士有事,为啥不安排让我昨天和令狐冲见面呢?

          负责人说:不行!俺们讲究的是先来后到! 因为任女士先约的令狐冲,甭管怎么着,你都得排在任女士后面

          藤篮很生气! 于是来到了异步相亲机构。

        3 异步相亲机构:这个机构的负责人则很灵活:

          一般情况下遵从先来后到的理念,特殊情况特殊对待。

          藤篮很喜欢这个负责人的理念,于是和这里的负责人携手一生.......

        4 再来总结一下:Javascript语言的执行环境是"单线程",所谓"单线程",就是指一次只能完成一件任务。

          同步就是: 后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的

          异步就是:一个任务可能有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而执行回调函数

            后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的

      3  上面异步中讲到,一个任务可能有一个或多个回调函数,假如a任务有a1,a2,a3这三个回调函数,b也需要a1,a2,a3这三个回调函数。我们需要这样做:

        function a1(){
            
        }
        function a2(){
            
        }
        function a3(){
            
        }
        var a = setTimeout(function(){
            a1();
            a2();
            a3();
        },1000);
        var b = setTimeout(function(){
            a1();
            a2();
            a3();
        },2000)
    View Code

      4  上面的代码很麻烦是不是?Callback就是来解决这种麻烦!!

    Callbacks如何解决这种麻烦? 

    先来说一下大体思路:

      1  首先我们每次调用Callbacks(),都会返回一个callbacks对象,这个对象有一个仅仅只有自己才能访问的数组(就是用了闭包呗) 

      2  这个数组就是用来存储 回调函数的。

      3  callbacks对象有许多方法,可以对数组进行操作,比如说:添加(add),清空(empty),删除(remove),运行数组中的所有回调函数(fire)....... 

      4  那么,我们上面---为什么搞了个Callbacks函数?---的第三条  任务a,任务b可以如下改写:

        function a1(){
    
        }
        function a2(){
    
        }
        function a3(){
    
        }
        var allCallback = $.Callbacks();
        allCallback.add(a1,a2,a3); 
        
        var a = setTimeout(function(){
            allCallback.fire()
        },1000);
        var b = setTimeout(function(){
            allCallback.fire()
        },2000)
    View Code

    升华:

      Callbacks可不仅仅只实现了这些,他还提供了很多参数: var a = Callbacks('once memory unique stopOnFalse ')

        once: 如果创建Callbacks时加入该参数,则运行数组中的所有回调函数之后,也就是fire()之后,会清空数组。

        memory: 会保存上一次运行fire(args)时的参数args,每当添加一个新的回调函数到数组中,会立即使用args作为参数调用新加的函数一次

        unique: 确保数组中的回调函数互不相同

        stopOnFalse: 当运行fire()时,若某个回调函数返回false,则立即终止余下的回调函数执行

      尽管这些参数可能有些初看没啥用。 但是不得要说,jQuery的开发人员真的很细致!

    源码讲解:  

        define([
        "./core",
        "./var/rnotwhite"
    ], function( jQuery, rnotwhite ) {
    
    // String to Object options format cache
    var optionsCache = {};
    
    // Convert String-formatted options into Object-formatted ones and store in cache
    /*
    如果: var a = $.Callback('once memory')
    则 optionsCache中会有这么一项:"once memory":{memory:true,once:true}
    */
    function createOptions( options ) {
        var object = optionsCache[ options ] = {};
        jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
            object[ flag ] = true;
        });
        return object;
    }
    
    /*
     * Create a callback list using the following parameters:
     *
     *    options: an optional list of space-separated options that will change how
     *            the callback list behaves or a more traditional option object
     *
     * By default a callback list will act like an event callback list and can be
     * "fired" multiple times.
     *
     * Possible options:
     *
     *    once:            will ensure the callback list can only be fired once (like a Deferred)
     *
     *    memory:            will keep track of previous values and will call any callback added
     *                    after the list has been fired right away with the latest "memorized"
     *                    values (like a Deferred)
     *
     *    unique:            will ensure a callback can only be added once (no duplicate in the list)
     *
     *    stopOnFalse:    interrupt callings when a callback returns false
     *
     */
    jQuery.Callbacks = function( options ) {
    
        // Convert options from String-formatted to Object-formatted if needed
        // (we check in cache first)
        options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );
    
        var // Last fire value (for non-forgettable lists)
            memory,
            // Flag to know if list was already fired  list是否已经被fire函数调用过
            fired,
            // Flag to know if list is currently firing  当前是否正在调用fire函数
            firing,
            // First callback to fire (used internally by add and fireWith)  第一个被执行的回调函数在list的位置
            firingStart,
            // End of the loop when firing   fire函数要运行的回调函数的个数
            firingLength,
            // Index of currently firing callback (modified by remove if needed)  当前正在执行的回调函数的索引
            firingIndex,
            //回调函数数组
            list = [],
            // Stack of fire calls for repeatable lists  可重复的回调函数栈。我们可能会短时间内执行多次fire(),若当前的fire()正在迭代执行回调函数,而紧接着又执行了一次fire()时,会将下一次的fire()参数等保存至stack中,等待当前的fire()执行完成后,将stack中的fire()进行执行
            stack = !options.once && [],
            // Fire callbacks
            fire = function( data ) {
                // data[0] 是一个对象,data[1]则是回调函数的参数
                memory = options.memory && data;  // 很精妙,仔细体会一下这句代码,如果调用Calbacks时传入了memory,则memory = data,否则memory = false
                fired = true; // 在调用本函数时,将fired状态进行修改
                firingIndex = firingStart || 0;
                firingStart = 0;
                firingLength = list.length;
                firing = true; // 迭代回调函数之前,将firing状态进行修改
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false &&
                        options.stopOnFalse ) { // 运行回调函数的同时,检测回调函数是否返回false,若返回false,且调用Callbacks时传入stopOnFalse参数,则终止迭代
    
                        memory = false; // To prevent further calls using add 既然终止迭代了,那么之后添加的回调函数都不应该被调用,将memory设置为false
                        break;
                    }
                }
                firing = false;  // 迭代回调函数完成后,将firing状态进行修改
                if ( list ) {
                    if ( stack ) {  // 没有使用once参数
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    } else if ( memory ) { // 使用了once memory参数,则在迭代完回调函数之后清空list
                        list = [];
                    } else { // 其他
                        self.disable();
                    }
                }
            },
            // Actual Callbacks object
            self = {
                // 将一个新的回调函数添加至list
                add: function() {
                    if ( list ) {
                        // First, we save the current length  首先,我们将当前的长度进行保存
                        var start = list.length;
                        (function add( args ) {  // 自执行函数
                            jQuery.each( args, function( _, arg ) {
                                var type = jQuery.type( arg );
                                if ( type === "function" ) {
                                    if ( !options.unique || !self.has( arg ) ) {
                                        list.push( arg ); // 若参数中的元素为函数且(无unique参数或者list中没有该函数),则将该函数添加至list末尾
                                    }
                                } else if ( arg && arg.length && type !== "string" ) { //  arg的长度不为0且每项的类型不为字符串,也就是args为这种情况:[[fun1,fun2...],[fun3,fun4]](不仅限于这种情况)
                                    // Inspect recursively
                                    add( arg );
                                }
                            });
                        })( arguments );
                        // Do we need to add the callbacks to the
                        // current firing batch?
                        // 当Callback中的firingLength变为 动态的! 也就是:只要我们向list中添加了一个新的回调函数,即使在fire()运行过程中,改变也能立即体现出来
                        if ( firing ) {
                            firingLength = list.length;
                        // With memory, if we're not firing then
                        // we should call right away
                        } else if ( memory ) { // 如果当前没有执行回调函数,且存在memory参数,则执行新添加的回调函数
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },
                // Remove a callback from the list 将一个回调函数从list中移除
                remove: function() {
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );
                                // Handle firing indexes
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },
                // Check if a given callback is in the list.
                // If no argument is given, return whether or not list has callbacks attached.
                has: function( fn ) {
                    return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
                },
                // Remove all callbacks from the list 清空数组
                empty: function() {
                    list = [];
                    firingLength = 0;
                    return this;
                },
                // Have the list do nothing anymore  使用了这个方法,则意味着该回调对象失效了。
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },
                // Is it disabled?
                disabled: function() {
                    return !list;
                },
                // Lock the list in its current state 给数组上锁
                lock: function() {
                    stack = undefined;
                    if ( !memory ) {
                        self.disable();
                    }
                    return this;
                },
                // Is it locked?
                locked: function() {
                    return !stack;
                },
                // Call all callbacks with the given context and arguments
                // 使用传入的context作为当前函数的执行上下文
                fireWith: function( context, args ) {
                    if ( list && ( !fired || stack ) ) {
                        args = args || [];
                        args = [ context, args.slice ? args.slice() : args ];
                        if ( firing ) {
                            stack.push( args ); // 如果当前正在迭代执行回调函数,则将新的fire参数推入stack中
                        } else {
                            fire( args );
                        }
                    }
                    return this;
                },
                // Call all the callbacks with the given arguments
                fire: function() {
                    self.fireWith( this, arguments );
                    return this;
                },
                // To know if the callbacks have already been called at least once
                // 用来确定当前callback对象是否被fire()过
                fired: function() {
                    return !!fired;
                }
            };
    
        return self;
    };
    
    return jQuery;
    });

     

  • 相关阅读:
    HTML 5 音频
    HTML 5 视频
    HTMl链接- target/ name
    HTML 链接
    OGNL_一点
    struts_表单得到数据
    MySql_十六进制值
    HTML 事件属性(下)
    作业3-2 输入一个正整数 n,再输入 n 个学生的成绩,计算平均成绩,并统计所有及格学生的人数
    作业3-1 .输入一个整数 x,计算并输出下列分段函数 sign(x) 的值
  • 原文地址:https://www.cnblogs.com/MnCu8261/p/6123492.html
Copyright © 2011-2022 走看看