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>
    
  • 相关阅读:
    MyEclipse中选择一行的快捷键
    myeclipse选中一行的内容,快速移动复制到某一行。
    为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?
    webpack-dev-server
    webpack-clean-webpack-plugin
    --save-dev和--save的区别
    extract-text-webpack-plugin 的使用及安装
    this 指向详细解析(箭头函数)
    CSS三:CSS的三种引入方式
    Webpack中的css-loader 和style-loader
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/6155117.html
Copyright © 2011-2022 走看看