最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

jQuery源码之回调函数的解析

来源:动视网 责编:小采 时间:2020-11-27 19:33:59
文档

jQuery源码之回调函数的解析

jQuery源码之回调函数的解析:这篇文章主要介绍了关于jQuery源码之回调函数的解析,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下回调函数一、概念回调函数是一个通过函数指针来调用执行的函数,如果你把一个函数的指针作为参数传递出去,那么这个指针调用这个函数的时候
推荐度:
导读jQuery源码之回调函数的解析:这篇文章主要介绍了关于jQuery源码之回调函数的解析,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下回调函数一、概念回调函数是一个通过函数指针来调用执行的函数,如果你把一个函数的指针作为参数传递出去,那么这个指针调用这个函数的时候


对于$.Callbacks 创建的Callback对象,它的addfire方法就是,其实就是基于发布订阅(Publish/Subscribe)的观察者模式的设计。

// 模拟一下这种模式
function aa() {
 console.log('aa');
}
function bb() {
 console.log('bb');
}
var m_db = {
 Callbacks: [],
 add: function(fn) {
 this.Callbacks.push(fn);
 },
 fire: function() {
 this.Callbacks.forEach(function(fn){
 fn();
 })
 }
}
m_db.add(aa);
m_db.add(bb);
m_db.fire();
  • 设计原理

  • 开始构建一个存放回调的数组,如this.callbacks= [] 添加回调时,将回调push进this.callbacks,执行则遍历this.callbacks执行回调,也弹出1跟2了。当然这只是简洁的设计,便于理解,整体来说设计的思路代码都是挺简单的,那么我们从简单的设计深度挖掘下这种模式的优势。


    模式的实际使用
    // 首先看一个场景
    $.ajax({
     url: '',
     ..
    }).done(function(data) {
     // 第一步处理数据
     
     // 第二步处理DOM
     $('aaron1').html(data.a)
     $('aaron2').html(data.b)
     $('aaron3').html(data.c) 
     
     // 其余处理
     
    })

    首先,所有的逻辑都写在done方法里面,这样确实是无可厚非的,但是问题就是逻辑太复杂了。Done里面有数据处理html渲染、还可能有其它不同场景的业务逻辑。这样如果是换做不同的人去维护代码,增加功能就会显得很混乱而且没有扩展性。

    $.ajax({
     url: '',
     ..
    }).done(function(data) {
     // 第一步处理数据
     processData(data);
     // 第二步处理DOM
     processDom(data);
     
     // 其余处理
     processOther(data);
    })

    这样看着时好一些了,通过同步执行来一次实现三个方面的处理,每一方面的处理都提取出来,但是这样的写法几乎就是“就事论事”的处理,达不到抽象复用。

    var m_cb = {
     callbacks: [],
     add: function(fn){
     this.callbacks.push(fn);
     },
     fire: function(data){
     this.callbacks.forEach(function(fn){
     fn(data);
     })
     }
    }
    m_cb.add(function(data){
     // 数据处理
    })
    m_cb.add(function(data){
     // DOM处理
     
    })
    
    m_cd.add(function(data){
     // 其余处理
    })
    $.ajax({
     url: '',
     ...
    }).done(function(data){
     m_cd.fire(data);
    })

    这样使用了观察者模式之后是不是感觉好多了呢,设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知。订阅者也称为观察者,而被观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者并且可能经常以事件对象的形式传递消息。

    总之、观察者模式就是将函数/业务处理管理起来,当一定的事件触发或者时某一任务执行完毕后,一次性执行。


    三、$.Callbacks()

    对于$.Callbacks 创建的Callback对象,它的addfire方法就是,其实就是基于发布订阅(Publish/Subscribe)的观察者模式的设计。

    $.Callbacks一般的开发者使用的较少,它的开发实现主要时为$.ajax以及$.deferred

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

    这个函数常使用的就是在事件触发机制中,也就是观察者设计模式的订阅和发布模式中,$.Callbacks主要出现在ajax、deferred、queue中。

  • 下面来仔细分析一下该方法的使用吧

  • 1、先来跑一下流程
    function aa() {
     console.log('aa');
    }
    function bb() {
     console.log('bb');
    }
    
    var cb = $.Callbacks();
    cb.add(aa);
    cb.add(bb);
    cb.fire(); 
    // aa
    // bb
    function fn1(value) {
     console.log(value);
    }
    
    function fn2(value) {
     fn1("fn2 says: " + value);
     return false;
    }
    
    var cb1 = $.Callbacks();
    cb1.add(fn1); // 添加一个进入队列
    cb1.fire('foo'); // 执行一下
    // foo
    cb1.add(fn2); // 再添一个
    cb1.fire('bar'); // 一次性执行
    // bar
    // fn2 says: bar
    cb1.remove(fn2); // 移除一个
    cb1.fire('111'); // 执行剩下的那一个
    // 111

    $.Callbacks()就是一个工厂函数。

  • 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;
    };
  • 参数处理

  • // 处理通过空格分隔的字符串
    var str = "once queue";
    var option = {};
    $.each(str.match(/\S+/g) || [], function (_index, item) {
     option[item] = true;
    })
    console.log(option);
    // {once: true, queue: true}
    Callbacks内部维护着一个List数组。这个数组用于存放我们订阅的对象,它是通过闭包来实现长期驻存的。添加回调时,将回调push进list,执行则遍历list执行回调。

    Callbacks 有4个参数。

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

    var callbacks = $.Callbacks('once');
    
    callbacks.add(function() {
     alert('a');
    })
    
    callbacks.add(function() {
     alert('b');
    })
    
    callbacks.fire(); //
    输出结果: 'a' 'b' callbacks.fire(); //未执行
    // 来看一下具体怎么实现
    // jQuery是在执行第一个fire的时候直接给清空list列表了,然后在add的地方给判断下list是否存在,从而达到这样的处理
    function Callbacks(options){
     var list = [];
     var self = {};
     self: {
     add: function(fn){
     list.push(fn);
     },
     fire: function(data){
     this.list.forEach(function(item){
     item(data);
     })
     if(options == 'once') {
     list = undefined;
     }
     }
     
     }
     return self;
    }
    // $jQuery.Callbacks的处理,在fire中调用了 self.disable(); 方法
    // 禁用回调列表中的回调。
    disable: function() {
     list = stack = memory = undefined;
     return this;
    }
  • memory 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调

  • function fn1(val) {
     console.log('fn1 says ' + val);
    }
    function fn2(val) {
     console.log('fn2 says ' + val);
    }
    function fn3(val) {
     console.log('fn3 says ' + val);
    }
    
    var cbs = $.Callbacks('memory');
    cbs.add(fn1);
    cbs.fire('foo'); // fn1 says foo
    
    console.log('..........')
    
    cbs.add(fn2); // 这里在添加一个函数进入队列的同时,就立马执行了这个 回调了
    cbs.fire('bar'); 
    // fn2 says foo 这个东东比较特殊~
    // fn1 says bar
    // fn2 says bar
    
    console.log('..........')
    cbs.add(fn3);
    cbs.fire('aaron');
    // fn3 says bar
    // fn1 says aaron 
    // fn2 says aaron
    // fn3 says aaron
    // 需要解决的问题一个就是如何获取上一个参数,以及add后的执行
    function Callbacks(options) {
     var list = [];
     var self;
     var firingStart;
     var memory;
    
     function _fire(data) {
     memory = options === 'memory' && data;
     firingIndex = firingStart || 0; // 
     firingStart = 0;
     firingLength = list.length;
     for (; list && firingIndex < firingLength; firingIndex++) {
     list[firingIndex](data)
     }
     }
    
     self = {
     add: function(fn) {
     var start = list.length;
     list.push(fn)
     // 如果参数是memory
     if (memory) {
     firingStart = start; //获取最后一值
     _fire(memory); // 同时执行
     }
     },
     fire: function(args) {
     if (list) {
     _fire(args)
     }
     }
     }
     return self;
    }
  • Unique:确保一次只能添加一个回调(所以在列表中没有重复的回调)

  • function fn1(val) {
     console.log('fn1 says ' + val);
    }
    var callbacks = $.Callbacks( "unique" );
    callbacks.add( fn1 );
    callbacks.add( fn1 ); // repeat addition
    callbacks.add( fn1 );
    callbacks.fire( "foo" );
  • stopOnFalse: 当一个回调返回false 时中断调用

  • function fn1(value) {
     console.log(value);
     return false;
    }
    
    function fn2(value) {
     fn1("fn2 says: " + value);
     return false;
    }
    
    var callbacks = $.Callbacks("stopOnFalse");
    callbacks.add(fn1);
    callbacks.fire("foo");
    
    callbacks.add(fn2);
    callbacks.fire("bar");
    
    // foo
    // bar
    $.callback()的源码
    jQuery.Callbacks = function( options ) {
    
     // Convert options from String-formatted to Object-formatted if needed
     // (we check in cache first)
     //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用
     //如果是对象则通过jQuery.extend深复制后赋给options。
     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
     fired, // 列表中的函数是否已经回调至少一次
    
     // Flag to know if list is currently firing
     firing, // 列表中的函数是否正在回调中
    
     // First callback to fire (used internally by add and fireWith)
     firingStart, // 回调的起点
    
     // End of the loop when firing
     firingLength, // 回调时的循环结尾
    
     // Index of currently firing callback (modified by remove if needed)
     firingIndex, // 当前正在回调的函数索引
    
     // Actual callback list
     list = [], // 回调函数列表
    
     // Stack of fire calls for repeatable lists
     stack = !options.once && [],// 可重复的回调函数堆栈,用于控制触发回调时的参数列表
    
     // Fire callbacks// 触发回调函数列表
     fire = function( data ) {
     //如果参数memory为true,则记录data
     memory = options.memory && data;
     fired = true; //标记触发回调
     firingIndex = firingStart || 0;
     firingStart = 0;
     firingLength = list.length;
     //标记正在触发回调
     firing = true;
     for ( ; list && firingIndex < firingLength; firingIndex++ ) {
     if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
     // 阻止未来可能由于add所产生的回调
     memory = false; // To prevent further calls using add
     break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环
     }
     }
     //标记回调结束
     firing = false;
     if ( list ) {
     if ( stack ) {
     if ( stack.length ) {
     //从堆栈头部取出,递归fire
     fire( stack.shift() );
     }
     } else if ( memory ) {//否则,如果有记忆
     list = [];
     } else {//再否则阻止回调列表中的回调
     self.disable();
     }
     }
     },
     // Actual Callbacks object
     // 暴露在外的Callbacks对象,对外接口
     self = {
     // Add a callback or a collection of callbacks to the list
     add: function() { // 回调列表中添加一个回调或回调的集合。
     if ( list ) {
     // First, we save the current length
     //首先我们存储当前列表长度
     var start = list.length;
     (function add( args ) { //jQuery.each,对args传进来的列表的每一个对象执行操作
     jQuery.each( args, function( _, arg ) {
     var type = jQuery.type( arg );
     if ( type === "function" ) {
     if ( !options.unique || !self.has( arg ) ) { //确保是否可以重复
     list.push( arg );
     }
     //如果是类数组或对象,递归
     } else if ( arg && arg.length && type !== "string" ) {
     // Inspect recursively
     add( arg );
     }
     });
     })( arguments );
     // Do we need to add the callbacks to the
     // current firing batch?
     // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作
     // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时
     // 那么需要更新firingLength值
     if ( firing ) {
     firingLength = list.length;
     // With memory, if we're not firing then
     // we should call right away
     } else if ( memory ) {
     //如果options.memory为true,则将memory做为参数,应用最近增加的回调函数
     firingStart = start;
     fire( memory );
     }
     }
     return this;
     },
     // Remove a callback from the list
     // 从函数列表中删除函数(集)
     remove: function() {
     if ( list ) {
     jQuery.each( arguments, function( _, arg ) {
     var index;
     // while循环的意义在于借助于强大的jQuery.inArray删除函数列表中相同的函数引用(没有设置unique的情况)
     // jQuery.inArray将每次返回查找到的元素的index作为自己的第三个参数继续进行查找,直到函数列表的尽头
     // splice删除数组元素,修改数组的结构
     while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
     list.splice( index, 1 );
     // Handle firing indexes
     // 在函数列表处于firing状态时,最主要的就是维护firingLength和firgingIndex这两个值
     // 保证fire时函数列表中的函数能够被正确执行(fire中的for循环需要这两个值
     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
     // 以给定的上下文和参数调用所有回调函数
     fireWith: function( context, args ) {
     if ( list && ( !fired || stack ) ) {
     args = args || [];
     args = [ context, args.slice ? args.slice() : args ];
     //如果正在回调
     if ( firing ) {
     //将参数推入堆栈,等待当前回调结束再调用
     stack.push( args );
     } 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
     // // 回调函数列表是否至少被调用一次
     fired: function() {
     return !!fired;
     }
     };
     return self;
    };
    未完待续~~

    文档

    jQuery源码之回调函数的解析

    jQuery源码之回调函数的解析:这篇文章主要介绍了关于jQuery源码之回调函数的解析,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下回调函数一、概念回调函数是一个通过函数指针来调用执行的函数,如果你把一个函数的指针作为参数传递出去,那么这个指针调用这个函数的时候
    推荐度:
    标签: 代码 解析 函数的
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top