资讯专栏INFORMATION COLUMN

jQuery 源码系列(七)Callbacks 函数

timger / 2992人阅读

摘要:的支持的方法有几个主要的,和,比如官方有一个例子这两个作为函数调用的生成从基本可以看出,函数生成了一个对象,这个对象的方法是添加回调函数,而方法则是执行回调函数。

欢迎来我的专栏查看系列文章。

讲真,Sizzle 的源码真的太压抑了,以至于写 Sizzle 文章的这段时间里都非常的痛苦,刚开始觉得它还挺有意思的,越到后面越觉得代码很难读懂,烦。

寒假也过完了,在家里待了两周的时间,感觉不错,这期间学习的事情都抛在脑后,学得非常少,把 cctv 的《中国通史》系列节目给看完了,对于历史迷的我来说,也算是一种心安吧。

今天的主题不在时 Sizzle,停顿了两周,感觉清醒了很多,之前被 Sizzle 支配的痛苦已经消去大半,今天来介绍一下 jQuery 的 Callbacks 函数。

Callbacks 的使用

jQuery 内部提供了很多基础功能的方法,比如 $.ajax()、$.each() 和 $.Callbacks(),这些方法既可以在内部进行使用,又可以被开发者拿到外部多带带使用。

Callbacks 的支持的方法有几个主要的,add、fire、remove 和 disable,比如官方有一个例子:

// 这两个作为 callback 函数
function fn1( value ) {
  console.log( value );
}
 
function fn2( value ) {
  fn1("fn2 says: " + value);
  return false;
}

// 调用 jQuery 的 Callbacks 生成 callbacks
var callbacks = $.Callbacks();
callbacks.add( fn1 );

callbacks.fire( "foo!" );
// "foo!"
 
callbacks.add( fn2 );
 
callbacks.fire( "bar!" );
// "bar!"
// "fn2 says: bar!"

从基本 demo 可以看出,$.Callbacks() 函数生成了一个 callbacks 对象,这个对象的 .add() 方法是添加回调函数,而 .fire() 方法则是执行回调函数。

.remove() 方法是移除回调函数:

var callbacks = $.Callbacks();
callbacks.add( fn1 );

callbacks.fire( "foo!" );
// "foo!"
 
callbacks.add( fn2 );

callbacks.fire( "bar!" );
// "bar!"
// "fn2 says: bar!"

callbacks.remove( fn2 );

callbacks.fire( "foobar" );
// "foobar"

$.Callbacks() 还支持几个参数,表示执行回调的几种效果,$.Callbacks("once")

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

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

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

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

此方法还支持多个参数,比如$.Callbacks("once memory"),具体的使用请参考这个链接。

Callbacks 的源码

在放 jQuery 3.0 的源码之前,我们先来简单的模拟一下 Callbacks 函数,来实现其基本的功能:

var Callbacks = function(){
  var Cb = {
    callbacks: [],
    add: function(fn){
      this.callbacks.push(fn);
      return this;
    },
    fire: function(value){
      this.callbacks.forEach(function(fn){
        fn(value);
      });
      return this;
    }  
  }
  return Cb;
}

// 测试
var callbacks = Callbacks();
callbacks.add(fn1);
callbacks.fire("test"); //"test"

可以看到其实一个简单的 Callbacks 函数实现起来还是非常简单的。

整个的 Callbacks 源码其实大致如下:

jQuery.Callbacks = function(options){
  // 先对参数进行处理,比如 once、unique 等
  options = createOptions(options);

  // 参数定义,包括一些 flag 和 callbacks 数组
  var list = [], queue = [] ...

  // fire 是遍历数组,回掉函数的执行
  var fire = function(){
    ...
  }

  // self 是最终返回的对象
  var self = {
    add: function(){...},
    remove: function(){...},
    has: function(){...},
    disable: function(){...},
    fireWith: function(){...},//这个其实是 fire 函数的执行
    fire: function(){...}
    ...
  }
  return self;
}

因为前面已经简单的介绍过了如何实现一个基本的 Callbacks 函数,这里稍微清晰了一点,来看下 createOptions 函数,这个函数主要是对类似于 $.Callbacks("once memory")类型对 callback 进行 flag 分离:

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

其中 rnothtmlwhite 是一个正则表达式 /[^x20 f]+/g,用来获得所有的 flag 标志。createOptions 的结果是一个对象,键值分别是 flag 和 boolean。

那么现在的主要的问题,就全在那些 flag 上面来,"once memory unique stopOnFalse"

源码奉上:

jQuery.Callbacks = function(options) {
  // flag 处理
  options = typeof options === "string" ? createOptions(options) : jQuery.extend({}, options);

  var // Flag to know if list is currently firing
  firing,
    // Last fire value for non-forgettable lists
    memory,
    // Flag to know if list was already fired
    fired,
    // Flag to prevent firing
    locked,
    // Actual callback list
    list = [],
    // Queue of execution data for repeatable lists
    queue = [],
    // Index of currently firing callback (modified by add/remove as needed)
    firingIndex = -1,
    // Fire callbacks
    fire = function() {
      // 只执行一次,以后都不执行了
      locked = locked || options.once;

      // Execute callbacks for all pending executions,
      // respecting firingIndex overrides and runtime changes
      fired = firing = true;
      for (; queue.length; firingIndex = -1) {
        memory = queue.shift();
        while (++firingIndex < list.length) {
          // 回调执行函数,并检查是否 stopOnFalse,并阻止继续运行
          if (list[firingIndex].apply(memory[0], memory[1]) === false && options.stopOnFalse) {
            // Jump to end and forget the data so .add doesn"t re-fire
            firingIndex = list.length;
            memory = false;
          }
        }
      }

      // Forget the data if we"re done with it
      if (!options.memory) {
        memory = false;
      }

      firing = false;

      // locked 在这里实现
      if (locked) {
        // 虽然锁住但是是 memory,保留 list 以后使用
        if (memory) {
          list = [];
          // 拜拜...
        } else {
          list = "";
        }
      }
    },
    // Actual Callbacks object
    self = {
      // Add a callback or a collection of callbacks to the list
      add: function() {
        if (list) {
          // If we have memory from a past run, we should fire after adding
          if (memory && !firing) {
            firingIndex = list.length - 1;
            queue.push(memory);
          }

          (function add(args) {
            jQuery.each(args, function(_, arg) {
              if (jQuery.isFunction(arg)) {
                if (!options.unique || !self.has(arg)) {
                  list.push(arg);
                }
              } else if (arg && arg.length && jQuery.type(arg) !== "string") {
                // Inspect recursively
                add(arg);
              }
            });
          })(arguments);

          if (memory && !firing) {
            fire();
          }
        }
        return this;
      },
      // Remove a callback from the list
      remove: function() {
        jQuery.each(arguments, function(_, arg) {
          var index;
          while ((index = jQuery.inArray(arg, list, index)) > -1) {
            list.splice(index, 1);

            // Handle firing indexes
            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.length > 0;
      },
      // Remove all callbacks from the list
      empty: function() {
        if (list) {
          list = [];
        }
        return this;
      },
      // Disable .fire and .add
      // Abort any current/pending executions
      // Clear all callbacks and values
      disable: function() {
        locked = queue = [];
        list = memory = "";
        return this;
      },
      disabled: function() {
        return !list;
      },
      // Disable .fire
      // Also disable .add unless we have memory (since it would have no effect)
      // Abort any pending executions
      lock: function() {
        locked = queue = [];
        if (!memory && !firing) {
          list = memory = "";
        }
        return this;
      },
      locked: function() {
        return !!locked;
      },
      // Call all callbacks with the given context and arguments
      fireWith: function(context, args) {
        if (!locked) {
          args = args || [];
          args = [context, args.slice ? args.slice() : args];
          queue.push(args);
          if (!firing) {
            fire();
          }
        }
        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
      fired: function() {
        return !!fired;
      }
    };

  return self;
};

总的来说,这种 pub/sub 模式的代码还是比较容易看懂的,有些疑问的地方,比如源码中其实有两个数组,list 是队列数组,本应该叫做 queue,但是 queue 数组已经被定义,且 queue 的作用是用来存储 fire 执行时的参数,这点不能搞混。

还有就是当整个代码 firing 这个参数,导致当函数正在运行的时候,即执行两次 fire 的时候,需要补充 queue 元素,但 fire() 函数只执行一次。

总结

jQuery.Callbacks 沿用 jQuery 一贯的套路,最后 return self,刚看第一遍第二遍的时候,有点模模糊糊的,主要还是 once、memory 等 flag 参数干扰我的视线,尤其是其这些 flag 标志的实现,难受。

参考

jQuery.Callbacks 中文文档

jQuery 2.0.3 源码分析 回调对象 - Callbacks

本文在 github 上的源码地址,欢迎来 star。

欢迎来我的博客交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81650.html

相关文章

  • jQuery源码解析之$.queue()、$.dequeue()和jQuery.Callbacks(

    摘要:作为此时不存在,直接从数据缓存中获取并返回。作用是触发中的回调函数,的表示只让触发一次后,就需要清理,表示是将清空成空数组还是空字符。 showImg(https://segmentfault.com/img/remote/1460000019558449); 前言:queue()方法和dequeue()方法是为 jQuery 的动画服务的,目的是为了允许一系列动画函数被异步调用,但不...

    Karrdy 评论0 收藏0
  • jQuery源码解析之$.queue()、$.dequeue()和jQuery.Callbacks(

    摘要:作为此时不存在,直接从数据缓存中获取并返回。作用是触发中的回调函数,的表示只让触发一次后,就需要清理,表示是将清空成空数组还是空字符。 showImg(https://segmentfault.com/img/remote/1460000019558449); 前言:queue()方法和dequeue()方法是为 jQuery 的动画服务的,目的是为了允许一系列动画函数被异步调用,但不...

    itvincent 评论0 收藏0
  • jQuery源码学习之Callbacks

    摘要:源码学习之的通过回调实现异步,其实现核心是。回调函数队列中的函数返回时停止触发回调函数队列只能被触发一次记录上一次触发队列传入的值,新添加到队列中的函数使用记录值作为参数,并立即执行。实际是,内部则调用了在定义的局部函数。 jQuery源码学习之Callbacks jQuery的ajax、deferred通过回调实现异步,其实现核心是Callbacks。 使用方法 使用首先要先新建一个...

    lmxdawn 评论0 收藏0
  • jQuery 源码系列(十九)ajax 的相关操作

    摘要:欢迎来我的专栏查看系列文章。主要来看函数函数返回值有两个,其中就是发送函数了,一步一步,发送下来,无需多说明。参考源码分析系列整体结构源码分析系列总结触碰异步详解本文在上的源码地址,欢迎来。 欢迎来我的专栏查看系列文章。 关于 ajax,东西太多了,我本来想避开 ajax,避而不提,但觉得 ajax 这么多内容,不说又少点什么,就简单点来介绍吧,加上最近准备内推面试的时候,看了不少 a...

    Dean 评论0 收藏0
  • 新人领进门 - 收藏集 - 掘金

    摘要:笨办法学前端掘金最近在教任务班的学生造轮子。随着成为主流的组件发布平台,最全的知识汇总前端掘金引言一直是非常热门的话题,因此此系列文章主要从一些基本功能开始讲起,逐步深入了解的新概念。 笨办法学前端 - 掘金最近在教任务班的学生造轮子。我想干脆把源代码开源出来,让自学的同学也看一看吧。于是就有了《笨办法造轮子》系列教程。 没有 webpack、没有 babel、没有 SCSS、没有 r...

    Near_Li 评论0 收藏0

发表评论

0条评论

阅读需要支付1元查看
<