首頁  >  文章  >  web前端  >  jQuery源碼之回呼函數的解析

jQuery源碼之回呼函數的解析

不言
不言原創
2018-07-09 15:20:591339瀏覽

這篇文章主要介紹了關於jQuery原始碼之回呼函數的解析,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

回呼函數

一、概念

回呼函數是一個透過函數指標來呼叫執行的函數,如果你把一個函數的指標當作參數傳遞出去,那麼這個指標呼叫這個函數的時候,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。

好處: 使用回呼函數處理,程式碼就可以繼續進行其他任務,而無需空等。實際開發中,經常在javascript中使用非同步呼叫。

  • 非同步回呼

$(document).ready(callback);
$(document).on(‘click’,callback)

$.ajax({
  url: "aaron.html",
  context: document
}).done(function() { 
        //成功执行
}).fail(function() {
        //失败执行
);


$('#clickme').click(function() {
    $('#book').animate({
        opacity: 0.25,
        left: '+=50',
        height: 'toggle'
    }, 5000, function() {
        // Animation complete.
    });
});
  • #同步

  • ##
var test1 = function(callback) {
    //执行长时间操作
    callback();
}
test1(function() {
    //执行回调中的方法
});
一个同步(阻塞)中使用回调的例子,目的是在test1代码执行完成后执行回调callback

#所以理解回呼函數最重要的2點:1、一個回呼函數作為參數傳遞給另一個函數是,我們只是傳遞了函數定義。我們並沒有在參數中執行函數。我們並沒有傳遞像我們平常執行函數一樣帶有一對執行小括號()的函數2、回呼函數並不會馬上被執行,它會在包含它的函數內的某個特定時間點被「回調」。 二、觀察者模式在理解jquery的回呼物件之前我們先來學習觀察者模式(SB模式):

觀察者模式: 一個物件作為一個特定任務的觀察者,當這個任務出發或執行完畢之後通知觀察者(Subscriber)。 觀察者也可以叫做訂閱者,它指向被觀察者(Publisher),當事件發生時,

被觀察者
    會通知
  • 觀察者

  • 對於
$.Callbacks

所建立的

Callback
對象,它的
add

fire方法就是,其實就是基於發布訂閱(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渲染##​​#、可能還有

它不同場景的業務邏輯
。這樣如果是換做不同的人去維護程式碼,增加功能就會顯得很混亂而且沒有擴充性。 <pre class="brush:php;toolbar:false">$.ajax({     url: '',     .. }).done(function(data) {     // 第一步处理数据     processData(data);     // 第二步处理DOM     processDom(data);          // 其余处理     processOther(data); })</pre>這樣看著時好一些了,透過同步執行來一次實現三個方面的處理,每一方面的處理都提取出來,但是這樣的寫法幾乎就是「就事論事」的處理,達不到抽象復用。 <pre class="brush:php;toolbar:false">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); })</pre>這樣使用了觀察者模式之後是不是感覺好多了呢,設計該模式背後的主要動力是促進形成鬆散耦合。在這種模式中,並不是一個物件呼叫另一個物件的方法,而是一個物件訂閱另一個物件的特定活動並在狀態改變後獲得通知。訂閱者也稱為觀察者,而被觀察的物件稱為發布者或主題。當發生了一個重要的事件時,發布者將會通知(呼叫)所有訂閱者並且可能經常以事件物件的形式傳遞訊息。 總之、觀察者模式就是將函數/業務處理管理起來,當一定的事件觸發或時某一任務執行完畢後,一次執行。

三、$.Callbacks()對於$.Callbacks 所建立的Callback

對象,它的

addfire方法就是,其實就是基於發布訂閱(Publish/Subscribe)的觀察者模式的設計。 $.Callbacks一般的開發者使用的較少,它的開發實作主要時為$.ajax以及$.deferred

    jQuery.Callbacks
  • jquery

    在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(&#39;fn1 says &#39; + 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&#39;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;
};
未完待续~~

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

以上是jQuery源碼之回呼函數的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn