zoukankan      html  css  js  c++  java
  • JQuery源码解析(九)

    jQuery回调对象

    jQuery.Callbacks一般开发者接触的很少,虽然jQuery向开发者提供了外部接口调用,但是$.Callbacks()模块的开发目的是为了给内部$.ajax() 和 $.Deferred()模块提供统一的基本功能组件。它可以用来作为类似基础定义的新组件的功能。

    jQuery.Callbacks是jquery在1.7版本之后加入的,是从1.6版中的_Deferred对象中抽离的,主要用来进行函数队列的add、remove、fire、lock等操作,并提供once、memory、unique、stopOnFalse四个option进行一些特殊的控制。

    这个函数常见的应用场景是事件触发机制,也就是设计模式中的观察者模式的发布、订阅机制,目前Callbacks对象用于queue、ajax、Deferred对象中

    看官网提供的demo:

    function fn1(value) {
      console.log(value);
    }
    
    function fn2(value) {
      fn1("fn2 says: " + value);
      return false;
    }
    

     
    可以将上述两个方法作为回调函数,并添加到 $.Callbacks 列表中,并按下面的顺序调用它们:

    var callbacks = $.Callbacks();
    callbacks.add(fn1);
    // outputs: foo!
    callbacks.fire("foo!");
    callbacks.add(fn2);
    // outputs: bar!, fn2 says: bar!
    callbacks.fire("bar!")
    

     这样做的结果是,当构造复杂的回调函数列表时,将会变更很简单。可以根据需要,很方便的就可以向这些回调函数中传入所需的参数。

    上面的例子中,我们使用了 $.Callbacks() 的两个方法: .add() 和 .fire()。

    .add() 支持添加新的回调列表, 而.fire() 提供了一种用于处理在同一列表中的回调方法的途径。

    另一种方法是$.Callbacks 的.remove()方法,用于从回调列表中删除一个特定的回调。下面是.remove()使用的一个例子:

    var callbacks = $.Callbacks();
    callbacks.add( fn1 );
    // outputs: foo!
    callbacks.fire( "foo!" );
    callbacks.add( fn2 );
    // outputs: bar!, fn2 says: bar!
    callbacks.fire( "bar!" );
    callbacks.remove( fn2 );
    // only outputs foobar, as fn2 has been removed.
    callbacks.fire( "foobar" );
    

    这个运用内部就是观察者模式的一种设计实现,只是相对比较复杂。我们看看jQuery的回调函数到底为哪些模块服务?

    异步队列模块:

    Deferred: function(func) {
      var tuples = [
        // action, add listener, listener list, final state
        ["resolve", "done", jQuery.Callbacks("once memory"), "resolved"],
        ["reject", "fail", jQuery.Callbacks("once memory"), "rejected"],
        ["notify", "progress", jQuery.Callbacks("memory")]
      ],………….
    

    队列模块

    _queueHooks: function(elem, type) {
      var key = type + "queueHooks";
      return data_priv.get(elem, key) || data_priv.access(elem, key, {
        empty: jQuery.Callbacks("once memory").add(function() {
          data_priv.remove(elem, [type + "queue", key]);
        })
      });
    }
    

    Ajax模块

    ajax: function(url, options) {
      //省略代码
      deferred = jQuery.Deferred(),
      completeDeferred = jQuery.Callbacks("once memory")
        ..............
    }
    

     不难发现jQuery.Callbacks还提供“once memory”等参数用来处理:

    once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred)。

     memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred)。

      unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调)。

      stopOnFalse: 当一个回调返回false 时中断调用。

    var callbacks = $.Callbacks('once');
    
    callbacks.add(function() {
      alert('a');
    })
    
    callbacks.add(function() {
      alert('b');
    })
    
    callbacks.fire(); //输出结果: 'a' 'b'
    callbacks.fire(); //未执行
    

     
    once的作用是使callback队列只执行一次。

    代码:

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
    <title></title>
    </head>
    <body>
    
    <script type="text/javascript">
    
    function show(data) {
      $("body").append('<li>' + data + '</li>')
    }
    
    function fn1(value) {
      show(value);
    }
    
    function fn2(value) {
      fn1("fn2 says: " + value);
      return false;
    }
    
    var callbacks = $.Callbacks();
    callbacks.add(fn1);
    // outputs: foo!
    callbacks.fire("foo!");
    callbacks.add(fn2);
    // outputs: bar!, fn2 says: bar!
    callbacks.fire("bar!");
    
    </script>
    
    </body>
    </html>
    

    jQuery回调模块结构

    整个$.Callbacks的源码很少,它是一个工厂函数,使用函数调用(非new,它不是一个类)创建对象,它有一个可选参数flags用来设置回调函数的行为,

    对外的接口也就是self的返回。

    jQuery.Callbacks()的API列表如下:

    callbacks.add()        :回调列表中添加一个回调或回调的集合。
    callbacks.disable()    :禁用回调列表中的回调。
    callbacks.disabled()   :确定回调列表是否已被禁用。 
    callbacks.empty()      :从列表中删除所有的回调。
    callbacks.fire()       :用给定的参数调用所有的回调。
    callbacks.fired()      :访问给定的上下文和参数列表中的所有回调。 
    callbacks.fireWith()   :访问给定的上下文和参数列表中的所有回调。
    callbacks.has()        :确定列表中是否提供一个回调。
    callbacks.lock()       :锁定当前状态的回调列表。
    callbacks.locked()     :确定回调列表是否已被锁定。
    callbacks.remove()     :从回调列表中的删除一个回调或回调集合。
    

     源码结构:

    jQuery.Callbacks = function(options) {
        options = typeof options === "string" ?
            (optionsCache[options] || createOptions(options)) :
            jQuery.extend({}, options);
        //实现代码
        fire = function() {}
        self = {
            add: function() {},
            remove: function() {},
            has: function(fn) {},
            empty: function() {},
            disable: function() {},
            disabled: function() {},
            lock: function() {},
            locked: function() {},
            fireWith: function(context, args) {},
            fire: function() {},
            fired: function() {}
        };
        return self;
    };
    

    整个结构要分三部分:

           Options参数缓存

           内部fire触发器的设计

           外部

    参数的缓存设计

    Callbacks是可以是接受的字符串的组合传参数,可以使用空格分割,代码如下:

    var opts = 'unique memory';
    var object = {}
    jQuery.each(opts.match(/S+/g) || [], function(_, flag) {
      object[flag] = true;
    });
    

     这样的操作其实是不需要重复的,所以我们可以设计一个缓存池,用来储存重复的操作:

    var optionsCache = {};
    function createOptions(options) {
      var object = optionsCache[options] = {};
      jQuery.each(options.match(rnotwhite) || [], function(_, flag) {
        object[flag] = true;
      });
      return object;
    }
    

     所以我们传递参数的时候,如果参数是字符串,我们可以直接从optionsCache缓存中去查找:

    options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );
    

    接口的设计:

    通过学习了观察者模式的思路,我们知道callback需要在内部维护着一个list的队列数组,用于保存订阅的对象数据。同时也需要提供了add、remove、fire等订阅、发布、删除类似的接口。

    那么我们代码是不是很简单是就是把订阅对象给push给内部list列表?

    实现思路就是: 构建一个存放回调的数组,如var list = [],通过闭包使这条回调数组保持存在。添加回调时,将回调push进list,执行则遍历list执行回调。

    代码:

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
    <title></title>
    </head>
    <body>
    
    <script type="text/javascript">
    
    var optionsCache = {};
    var rnotwhite = (/S+/g);
    // Convert String-formatted options into Object-formatted ones and store in cache
    
    function show(data) {
      if (typeof data === 'object') {
        for (var key in data) {
          $("body").append('<li>key->' + key + '; value->'+ data[key] +'</li>')
        }
      } else {
        $("body").append('<li>' + data + '</li>')
      }
    }
    
    function createOptions(options) {
      var object = optionsCache[options] = {};
      jQuery.each(options.match(rnotwhite) || [], function(_, flag) {
        object[flag] = true;
      });
      return object;
    }
    
    function callback(options) {
      options = typeof options === "string" ?
        (optionsCache[options] || createOptions(options)) :
        jQuery.extend({}, options);
        show(options)
    }
    
    callback('once memory')
     
    }
    
    
    </script>
    
    </body>
    </html>
    
  • 相关阅读:
    Java实现 LeetCode 30 串联所有单词的子串
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 27 移除元素
    Java实现 LeetCode 27 移除元素
    字符编码终极笔记:ASCII、Unicode、UTF-8、UTF-16、UCS、BOM、Endian
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/6155117.html
Copyright © 2011-2022 走看看