这篇文章主要介绍了关于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渲染
、还可能有其它不同场景的业务逻辑
。这样如果是换做不同的人去维护代码,增加功能就会显得很混乱而且没有扩展性。
$.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
对象,它的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中。
下面来仔细分析一下该方法的使用吧
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个参数。
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; };
未完待续~~
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!
相关推荐:
以上是jQuery源码之回调函数的解析的详细内容。更多信息请关注PHP中文网其他相关文章!

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

SublimeText3汉化版
中文版,非常好用

WebStorm Mac版
好用的JavaScript开发工具

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)