Heim > Web-Frontend > js-Tutorial > Hauptteil

Analyse der Rückruffunktion des jQuery-Quellcodes

不言
Freigeben: 2018-07-09 15:20:59
Original
1336 Leute haben es durchsucht

Dieser Artikel stellt hauptsächlich die Analyse der Rückruffunktion des jQuery-Quellcodes vor. Sie hat einen gewissen Referenzwert. Jetzt kann ich sie mit allen teilen.

Rückruffunktion

1. Konzept

回调函数 ist eine Funktion, die über einen Funktionszeiger aufgerufen und ausgeführt wird. Wenn Sie einen Funktionszeiger als Parameter übergeben, nennen wir ihn einen Rückruf, wenn der Zeiger diese Funktion aufruft . Funktion. Die Rückruffunktion wird nicht direkt vom Implementierer der Funktion aufgerufen, sondern von einer anderen Partei, wenn ein bestimmtes Ereignis oder eine bestimmte Bedingung eintritt, um auf das Ereignis oder die Bedingung zu reagieren.

好处: Mithilfe der Callback-Funktion zur Verarbeitung kann der Code weiterhin andere Aufgaben ausführen, ohne vergeblich warten zu müssen. In der tatsächlichen Entwicklung werden in JavaScript häufig asynchrone Aufrufe verwendet.

  • Asynchroner Rückruf

$(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.
    });
});
Nach dem Login kopieren
  • Synchronisieren

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

Die beiden wichtigsten Punkte zum Verständnis der Callback-Funktion sind also:

1. Wenn eine Callback-Funktion als Parameter an eine andere Funktion übergeben wird, übergeben wir nur die Funktionsdefinition. Wir führen die Funktion nicht für die Parameter aus. Wir übergeben eine Funktion nicht mit einem Paar Ausführungsklammern (), wie wir normalerweise Funktionen ausführen

2. Die Rückruffunktion wird nicht sofort ausgeführt, sondern zu einem bestimmten Zeitpunkt innerhalb der Funktion, die sie enthält Punkt wird „zurückgerufen“.

2. Beobachtermodus

Bevor wir das Callback-Objekt von jquery verstehen, lernen wir zunächst den Beobachtermodus (SB-Modus) kennen:

Beobachtermodus: Ein Objekt fungiert als A 观察者 für eine bestimmte Aufgabe, die 观察者 (Abonnent) benachrichtigt, wenn die Aufgabe gestartet oder abgeschlossen wird. 观察者 kann auch 订阅者 heißen, was auf 被观察者 (Herausgeber) verweist. Wenn ein Ereignis auftritt, benachrichtigt 被观察者 观察者.

Für das von $.Callbacks erstellte Callback-Objekt sind seine Methoden add und fire tatsächlich auf der Grundlage des Beobachtermusters von Publish und Subscribe (Publish/Subscribe) entworfen.

// 模拟一下这种模式
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();
Nach dem Login kopieren
  • Designprinzip

Beginnen Sie mit dem Aufbau eines Arrays zum Speichern von Rückrufen, z. B. this.callbacks= [] Wenn Sie einen Rückruf hinzufügen, drücken Sie die Taste Rückruf in this.callbacks, wenn es ausgeführt wird, durchqueren Sie this.callbacks, um Rückrufe auszuführen, und 1 und 2 werden ebenfalls angezeigt. Natürlich ist dies nur ein einfaches Design, das leicht zu verstehen ist. Insgesamt sind die Designidee und der Code recht einfach, also schauen wir uns die Vorteile dieses Modells anhand des einfachen Designs an.


Tatsächliche Verwendung des Musters
// 首先看一个场景
$.ajax({
    url: '',
    ..
}).done(function(data) {
    // 第一步处理数据
    
    // 第二步处理DOM
    $('aaron1').html(data.a)
    $('aaron2').html(data.b)
    $('aaron3').html(data.c)   
    
    // 其余处理
    
})
Nach dem Login kopieren

Zunächst ist die gesamte Logik in der Methode done geschrieben, was verständlich ist, aber das Problem ist, dass die Logik zu kompliziert ist. Done enthält 数据处理, html渲染 und möglicherweise 其它不同场景的业务逻辑. Wenn also verschiedene Personen den Code pflegen würden, wäre das Hinzufügen von Funktionen verwirrend und nicht skalierbar.

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

Auf diese Weise werden drei Aspekte der Verarbeitung gleichzeitig durch synchrone Ausführung realisiert, und jeder Aspekt der Verarbeitung wird extrahiert. Diese Art des Schreibens ist jedoch fast eine „faktenbasierte“ Verarbeitung. was einer abstrakten Wiederverwendung nicht entspricht.

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);
})
Nach dem Login kopieren

Fühlen Sie sich viel besser, nachdem Sie das Beobachtermuster auf diese Weise verwendet haben? Die Hauptmotivation hinter der Gestaltung dieses Musters besteht darin, die Formation zu fördern松散耦合. Bei diesem Muster ruft ein Objekt nicht die Methoden eines anderen Objekts auf, sondern ein Objekt abonniert die spezifischen Aktivitäten eines anderen Objekts und wird benachrichtigt, wenn sich der Status ändert. Abonnenten werden auch Beobachter genannt, und die beobachteten Objekte werden Herausgeber oder Themen genannt. Wenn ein wichtiges Ereignis eintritt, benachrichtigt (ruft) der Herausgeber alle Abonnenten an und übermittelt möglicherweise Nachrichten häufig in Form von Ereignisobjekten.

Kurz gesagt besteht der Beobachtermodus darin, Funktionen/Geschäftsprozesse zu verwalten und sie alle auf einmal auszuführen, wenn ein bestimmtes Ereignis ausgelöst wird oder wenn eine bestimmte Aufgabe abgeschlossen ist.


3. $.Callbacks()

Für das von $.Callbacks erstellte Callback-Objekt gelten tatsächlich die Methoden add und fire Design basierend auf dem Beobachtermuster von Veröffentlichen und Abonnieren (Publish/Subscribe).

$.Callbacks wird von allgemeinen Entwicklern selten verwendet, und seine Entwicklung und Implementierung erfolgt hauptsächlich $.ajax und $.deferred.

jQuery.Callbacks wurde nach jquery Version 1.7 hinzugefügt. Es wurde aus dem _Deferred-Objekt in Version 1.6 extrahiert. Es wird hauptsächlich zum Ausführen von Operationen wie add、remove、fire、lock von Funktionswarteschlangen verwendet und stellt Vier once、memory、unique、stopOnFalse führen einige spezielle Kontrollen durch. option

Diese Funktion wird häufig im Ereignisauslösemechanismus verwendet, d. h. im Abonnement- und Veröffentlichungsmodus des Beobachter-Entwurfsmusters $.Callbacks erscheint hauptsächlich in Ajax, Deferred und Queue.

  • Lassen Sie uns die Verwendung dieser Methode im Detail analysieren

1、先来跑一下流程
function aa() {
    console.log('aa');
}
function bb() {
    console.log('bb');
}

var cb = $.Callbacks();
cb.add(aa);
cb.add(bb);
cb.fire(); 
// aa
// bb
Nach dem Login kopieren
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
Nach dem Login kopieren

$.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()     :从回调列表中的删除一个回调或回调集合。
Nach dem Login kopieren
  • 源码结构

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;
};
Nach dem Login kopieren
  • 参数处理

// 处理通过空格分隔的字符串
var str = "once queue";
var option = {};
$.each(str.match(/\S+/g) || [], function (_index, item) {
    option[item] = true;
})
console.log(option);
// {once: true, queue: true}
Nach dem Login kopieren
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(); //未执行
Nach dem Login kopieren
// 来看一下具体怎么实现
// 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;
}
Nach dem Login kopieren
// $jQuery.Callbacks的处理,在fire中调用了 self.disable(); 方法
// 禁用回调列表中的回调。
disable: function() {
    list = stack = memory = undefined;
    return this;
}
Nach dem Login kopieren
  • 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
Nach dem Login kopieren
// 需要解决的问题一个就是如何获取上一个参数,以及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;
}
Nach dem Login kopieren
  • 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" );
Nach dem Login kopieren
  • 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
Nach dem Login kopieren
$.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;
};
Nach dem Login kopieren
未完待续~~

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

相关推荐:

Das obige ist der detaillierte Inhalt vonAnalyse der Rückruffunktion des jQuery-Quellcodes. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage