jQuery Callbacks:

简单理解为:一个容器,里面可以存放多个函数,有add方法往容器里面填加函数,有fire方法把容器里面的所有函数都依次执行一遍。
创建容器:$.Callback([string])
创建一个Callback实例,可选传参数:

  • once : 当调用fire方法时,容器里的函数只执行1次
  • unique:去重,执行add方法时,检测有没有添加过
  • stopOnfalse :当调用fire方法时,容器里的函数返回值为false时,停止执行后面的函数
  • memory:当执行过一次fire后,再执行add时,会立即执行添加的新函数

Note:也可以理解为一个队列,不过队列的概念比较大,Callbacks并不具有队列的所有特性
使用方法:参数可以组合使用,例如:”once memory”

  1. let cb = $.Callbacks();
  2. $.Callbacks('once')
  3. $.Callbacks('unique')
  4. $.Callbacks('stopOnfalse')
  5. $.Callbacks('memory')
  6. $.Callbacks('memory once')

实现:

  1. 第一个版本:尝试写一个简单的Callbacks。
  1. function Callbacks(){
  2. let list = [];
  3. //返回一个有add和fire方法的对象,因为每次调用callbacks都需要是一个全新的对象
  4. return {
  5. add:function(fn){
  6. if(toString.call(fn)==='[object Function]'){
  7. list.push(fn);
  8. }
  9. },
  10. fire:function(){
  11. list.forEach(fn=>fn());
  12. }
  13. };
  14. }
  15. let cbs = Callbacks();
  16. cbs.add(function(){
  17. console.log('Hello!');
  18. })
  19. cbs.add(function(){
  20. console.log('How are you?');
  21. })
  22. cbs.fire() //Hello! How are you?
  23. cbs.fire() //Hello! How are you? (再次调用fire,则再次执行容器中所有的方法)

在上面的例子中,并没有考虑到Callbacks的可选参数,也就是要告诉容器要遵循的运行规则,共有四种规则:once、unique、stopOnfalse、memory,而且这些规则可以组合使用。

  1. 第二个版本: 考虑4种规则。
  1. // rules: once, unique, memory, stopOnFalse
  2. function Callbacks(rules) {
  3. var list = [];
  4. var rObj = createRules(rules);
  5. var isFired = false;
  6. return {
  7. add: function(fn) {
  8. if(toString.call(fn) === '[object Function]') {
  9. // 需要检查unique
  10. if(rObj.unique) {
  11. list.indexOf(fn) === -1 && list.push(fn);
  12. }
  13. else {
  14. list.push(fn);
  15. }
  16. // 如果有memory,且fire执行过,则立即执行当前fn
  17. if(rObj.memory && isFired) {
  18. fn();
  19. }
  20. }
  21. return this;
  22. },
  23. fire: function() {
  24. if(!rObj.once || !isFired) {
  25. for(var i = 0; i< list.length; i++) {
  26. if(list[i]() === false && rObj.stopOnFalse) {
  27. break;
  28. }
  29. }
  30. isFired = true;
  31. }
  32. }
  33. };
  34. //通过callback('once memory')创建容器时,此时obj:{once:true,memory:true}
  35. function createRules(rules) {
  36. var rs = {};
  37. if(typeof rules === 'string') {
  38. rules.split(/\s+/).forEach((r) => {
  39. rs[r] = true;
  40. });
  41. }
  42. return rs;
  43. }
  44. }
  45. var cb1 = new Callbacks('unique');
  46. var f1 = function() {
  47. console.log('hello');
  48. return false;
  49. };
  50. var f2 = function() {
  51. console.log('How are you!');
  52. return false;
  53. };
  54. cb1.add(f1);
  55. cb1.add(f2);
  56. cb1.add(f1);
  57. cb1.fire();

在上面的例子中,Callbacks功能已经很完善了,支持了4中规则,还可以组合搭配使用。

  1. Callbacks在 jQuery中的实现
  1. var cb = _.callbacks('memory');
  2. cb.add(function(){
  3. console.log('AAA');
  4. });
  5. cb.add(function(){
  6. console.log('BBB');
  7. return false;
  8. });
  9. cb.fire();
  10. cb.add(function(){
  11. console.log('CCC');
  12. });
  13. cb.add(function(){
  14. console.log('DDD');
  15. });
  1. (function(root) {
  2. var optionsCache = {};
  3. var _ = {
  4. callbacks: function(options){
  5. options = typeof options === 'string' ? (optionsCache[options] || createOptions(options)) : {};
  6. var list = [], testing, length, index, start, starts, memory;
  7. var fire = function(data) {
  8. memory = options.memory && data;
  9. testing = true;
  10. length = list.length;
  11. index = starts || 0; // 执行下标
  12. start = 0;
  13. for(; index < length; index++) {
  14. if(list[index].apply(data[0], data[1]) === false && options.stopOnFalse) {
  15. break;
  16. }
  17. }
  18. }
  19. var self = {
  20. add: function() {
  21. // add可以传入一个function,也可能多个function; 将auguments转成真正的数组
  22. var args = Array.prototype.slice.call(arguments);
  23. start = list.length;
  24. args.forEach((fn) => {
  25. if(toString.call(fn) === '[object Function]') {
  26. list.push(fn);
  27. }
  28. });
  29. // 是否为memory,如果是,执行下标要从队列的最后一个函数执行
  30. // 如果是'memory',第一次add的时候,memory为空,因为没有fire过,所以不会执行这里
  31. if(memory) {
  32. starts = start;
  33. fire(memory);
  34. }
  35. },
  36. fireWith: function(context, arguments) {
  37. var agrs = [context, arguments];
  38. if(!options.once || !testing) {
  39. fire(agrs);
  40. }
  41. },
  42. fire: function() {
  43. self.fireWith(this, arguments);
  44. }
  45. };
  46. return self;
  47. }
  48. }
  49. //通过callback('once memory')创建容器时,此时obj:{once:true,memory:true}
  50. function createOptions(options) {
  51. var object = optionsCache[options] = {};
  52. if(typeof options === 'string') {
  53. options.split(/\s+/).forEach((r) => {
  54. object[r] = true;
  55. });
  56. }
  57. return object;
  58. }
  59. root._ = _;
  60. })(this);

jQuery Callbacks 设计图:
可以把Callbacks理解为一个容器,或者一个队列(但队列的概念相对比较复杂),理解为容器更为简单和贴切。
image.png

Callbacks:并不是给用户专门使用的,而是对Deferred对象提供一种支持。

image.png

和ES6的Promise很像,设计灵感来自Deferred对象。