发布网友 发布时间:2022-04-19 20:08
共3个回答
懂视网 时间:2022-05-14 22:25
对于$.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渲染
、还可能有其它不同场景的业务逻辑
。这样如果是换做不同的人去维护代码,增加功能就会显得很混乱而且没有扩展性。
$.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
创建的Callback
对象,它的add
和fire
方法就是,其实就是基于发布订阅(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中。
下面来仔细分析一下该方法的使用吧
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个参数。
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; };未完待续~~
热心网友 时间:2022-05-14 19:33
因为javascript的运行机制的原因,jquery作为成熟的工具,为javascript提供了大量的回调函数(callback)。
因为回调函数的工作原理以及工作时机,很多新手对回调函数不能熟练运用。回调函数的重要作用之一,就是在某项操作完成时,进行下一项操作。这两项操作功能上不一定耦合,但是逻辑上却必须耦合。意思是前一个操作实现的功能(效果)可以和回调函数实现的功能毫无联系,但是二者在代码逻辑上却是耦合的。耦合的意思是说,当一操作进行后,二操作会紧跟进行,二者的运行关系密不可分。回调函数增加了程序的耦合性,使代码的逻辑更容易理解。
回调函数的用处在javascript中可谓处处体现,比如最为重要的就是AJAX回调。
Jquery中提供了大量大回调函数,比如complete,success,fail等操作。这些操作的调用时机就如同他们的名字一样。success以为着成功的时候调用,等。
举个简单的例子,以jquery中的动画为例,现在我想实现一个让id为animate的dom对象宽度边长的动画,在这个对象变长之后,我想要提示用户,这个对象已经变长了。因此,我得使用以下的代码:
$("#animate").animate({以上的代码定义了两个回调函数success和error,顾名思义,success是在成功的时候就会调用,error是在出错误之后调用。
这就是回调函数的基本运用。
看到这儿,或许你会问,为什么我不直接用这样的代码呢?
try {如果你测试你会发现,动画并未完成,就出现了alert弹窗。
这就是javascript的运行机制所造成的,javascript的运行机制分为同步和异步,异步操作会破坏脚本的执行流,使得程序跳过异步(异步仍然在进行)而进入接下来的程序,异步操作即使在进行,也不会影响javascript的继续执行,也就是不会产生阻塞。同步操作中的定时操作有着与异步很相似的运行机制,但是依然是同步操作。以上的错误正是由于定时操作引起的。
回调函数的作用,最为重要的运用,就是运用在异步操作和定时操作。
jquery中运用回调函数最重要的地方有两个,一个是动画回调(就是在动画执行完成,或者执行错误,或者执行失败的时候调用),以及ajax(同动画)。ajax与回调是密不可分的。
热心网友 时间:2022-05-14 20:51
举个js里面最简单的例子,