代码的本质突出顺序、有序这一概念,尤其在javascript——毕竟javascript是单线程引擎。
javascript拥有函数式编程的特性,而又因为javascript单线程引擎,我们的函数总是需要有序的执行。优秀代码常常 把函数切割成各自的模块,然后在某一特定条件下执行,既然这些函数是有序的执行,那么我们为什么不编写一个统一管理的对象,来帮助我们管理这些函数——于是,Callbacks(回调函数)诞生。
什么是Callbacks
javascript中充斥着函数编程,例如最简单的window.onload承接的就是一个函数,悲催的是window.onload直接赋值的话只能接收一个函数,如果有好几个函数想要在onload中执行,那么我们就需要编写如下代码:
复制代码 代码如下:
function a(elem) {
elem.innerHTML = '我是函数a,我要改变Element的HTML结构';
};
function b(elem) {
elem.innerHTML = '我的函数b,我要改变Element的style';
}
window.onload = function () {
var elem = document.getElementById('test');
a(elem);
b(elem);
};
回调函数初衷就是建立在这么个玩意儿的上面,不再让我们分散这些函数,而是把这些函数统一整理。可以看见,我们在window.onload中希望针对一个Element做两件事情:先改变html结构,然后改变这个html的style。两个函数同样是针对一个Element操作,而这两个函数最终的执行都是有序进行的。那么我们为什么不编写一个这样的对象管理这些函数呢。当然, 这只是回调函数的最基础的存在意义,我们需要的不仅仅是这样一个简单的回调函数对象,我们需要一个更加强大的回调函数。好吧,这只是一个简单的用例,那么我可以告诉你这个回调函数除了一个个执行函数之外,它还可以做什么。
Callbacks本质就是控制函数有序的执行,Javascript是单线程引擎,也就说,javascript同一时间只会有一处代码在运行——即便是Ajax、setTimeout。 这两个函数看起来好像都是异步的,其实并非如此,浏览器在运行javascript代码的时候,这些代码都会被有序的压入一个队列中,当你运行Ajax的时候,浏览器会把Ajax 压入代码队列,浏览器在处理javascript代码是从这个代码队列中一个一个取代码执行的——Callbacks,迎合了这种单线程引擎。
当然,我们要的,不仅仅是这样一个简单的工具对象——在jQuery源码中,Callbacks提供了一组函数的基本管理,为Deferred(异步队列)提供了基础,同时也服务于Queue(同步队列)。 Deferred用于抹平/扁平化金字塔编程(大量的回调函数嵌套,例如Ajax中需要根据请求返回码决定执行的代码); 而Queue,驱动着jQuery.animate(动画引擎)。
那么我们就来编写一个Callbacks吧。
Callbacks模型
Array(数组):
既然我们Callbacks要承接一系列函数,那么必然需要有一个容器。我们可以使用一个数组,并把每一个函数压到该数组中,需要执行的时候,循环数组项执行。
工作模型:
这个Callbacks需要非常的强大,并不仅仅是压入函数,然后执行这么简单,这个Callbacks应该拥有良好的执行模型。
once:当前Callbacks对象中所有的函数只会执行一次,执行一次完之后就会被释放掉,我们可以为使用Callbacks对象的用户提供一个稳定有效的方案,确保函数只会执行一次,之后不再执行,稳定了这些函数的线程。
auto:自动执行模型,这是个有意思的模型,有些函数依赖上一层函数,例如函数b的执行依赖于函数a,那么我们提供一个自动执行的模型:第一次执行这个Callbacks之后,每次添加函数到Callbacks的时候,自动执行过去添加的那些函数,并把最后一次给定的参数数据传递给过去的那些函数,这样就从Callbacks中抹平了这些依赖函数之间需要反复触发的关系,这是个有意思的模型。
once&auto:我们可以让它更强大,同时工作once和auto模型,即:当每次添加函数到Callbacks中的时候,过去的函数都会执行,然后,释放掉这些过去的函数,下次继续添加函数的时候,过去的那些函数不会再执行,因为once模型,已经把它们释放掉了。
API:
add(function) - 添加一个(或多个)函数到Callbacks对象中:当然,如果你并不添加函数只是好奇看看Callbacks,我们也将让你继续享受你的乐趣——我们并不会抛出异常,因为这对于我们来说并不擅长。
remove(function) - 移除一个Callbacks中的一个函数:既然有了添加,那么我们也应该提供反悔的方案,我们是多么的平易近人,容忍着别人过去所做的一切。
has(function) - 判断Callbacks中是否包含一个函数:哦?你竟然不确定是否包含这个函数,当初可是你丢进来的啊!你怎么如此马虎?不过既然你问我的话,我仍然会告诉你Callbacks是否包含这个函数,我知道你很繁忙,并不能记住和确定所有的事情。
empty() - 清空Callbacks:这些函数对于你失去了意义了么?什么?已经执行过你就不想要了?所以你希望可以清空它?好吧,为了内存君我还是忍下你这个需求。
disable() - 废掉一个Callbacks:为了和别人的代码稳定的存在,我选择了自我牺牲——没错,这个方法可以废掉Callbacks,彻底的废掉,就如同它曾经尚未存在过一般。
disabled() - 判断这个Callbacks是否已经被废掉:如果你仍然不相信Callbacks是否真的自我牺牲,那么这个方法可以让你安心。
lock(boolean) - 锁定这个Callbacks对象:你害怕它并不稳定,但是你又不想舍弃它,lock是个不错的方法,它接收一个Boolean的参数,表示是否需要锁定这个对象,当然,无参的它用于让你确定Callbacks是否被锁定。
fire(data) - 执行这个Callbacks中的函数:我们做的这一切,不都是为了这一刻执行的宿命么?参数将会成为这些需要执行的函数的参数。
fireWith(context,data) - 执行Callbacks中的函数,并且指定上下文。在fire()里,所有的函数的Context(上下文)都是Callbacks对象,而fireWidth(),可以让你重新定义这些要执行的函数的上下文,多么自由的编程啊,Callbacks为你考虑了一切。
fired() - 判断这个Callbacks过去是否已经执行过:我们相信,多数时候你并不知道过去做过什么,但是我们记录了你做的一切,如果你过去曾经执行过这个Callbacks对象,那么你休想否认,因为我们知道过去你是否执行了这个Callbacks。
基本模块实现
简单的实现:
我们先来简单的实现一个Callbacks:
复制代码 代码如下:
(function (window, undefined) {
var Callbacks = function () {
//通过闭包保护这些私有变量
var list = [],//回调函数列表
fired;//是否执行过
//返回一个闭包的Callbakcs对象
return {
add: function (fn) {
//当Callbacks废弃掉的时候,list为undefined
if (list) {
//添加一个回调函数
list.push(fn);
//支持链式回调
}
return this;
},
fireWith: function (context, data) {
//触发回调函数,并指定上下文
if (list) {
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
//当Callbacks中某一个函数返回false的时候,停止Callbacks后续的执行
if (list[i].apply(context, data) === false)
break;
}
}
return this;
},
fire: function () {
//触发回调函数
//调用fireWith并指定上下文
return this.fireWith(this, arguments);
},
empty: function () {
//清空list即可
if (list)//当这个Callbacks废弃掉的时候,Callbacks不应该可以继续使用
list = [];
return this;
},
disable: function () {
//废弃这个Callbacks对象,后续的回调函数列表不再执行
list = undefined;
return this;
},
disabled: function () {//检测这个Callbacks是否已经废掉
//转换为boolean返回
return !list;
},
fired: function () {//这个callbacks是否执行过
return !!fired;
}
};
};
//注册到window下
window.Callbacks = Callbacks;
}(window));
然后我们测试一下这个Callbacks:
复制代码 代码如下:
var test = new Callbacks();
test.add(function (value) {
console.log('函数1,value是:' + value);
});
test.add(function (value) {
console.log('函数2,value是:' + value);
});
test.fire('这是函数1和函数2的值');
console.log('查看函数是否执行过:' + test.fired());
test.disable();//废弃这个Callbacks
console.log('查看函数是否被废弃:' + test.disabled());
test.add(function () {
console.log('添加第三个函数,这个函数不应该被执行');
});
test.fire();
打开浏览器的控制台我们可以看见运行结果正常。
once和auto(memory)实现
once:
once让这个callbacks中的函数运行一次之后就不再运行。原理非常的简单,上面的代码中,我们可以看见有一个变量list承接函数列表,所以我们只需要把过去执行过的代码清空即可。我们用一个全局变量,保存当前执行模型,如果是once模型,就在fireWith()里让这个list失效即可:
复制代码 代码如下:
(function (window, undefined) {
var Callbacks = function (once) {
//通过闭包保护这些私有变量
var list = [],//回调函数列表
fired;//是否执行过
//返回一个闭包的Callbakcs对象
return {
//...省略部分代码
fireWith: function (context, data) {
//触发回调函数,并指定上下文
if (list) {
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
//当Callbacks中某一个函数返回false的时候,停止Callbacks后续的执行
if (list[i].apply(context, data) === false)
break;
}
}
//如果配置了once模型,则全局变量once为true,则list重置
if (once) list = undefined;
return this;
}
//...省略部分代码
};
};
//注册到window下
window.Callbacks = Callbacks;
}(window));
auto:
auto(memory)模型在jQuery中是以memory命名的,最初被这个命名给混淆了,仔细看了用法才确定改成auto——它的作用就是“第一次fire()之后,后续add()的函数自动执行”,以下情况可以用到:当添加一组函数到Callbacks之后,临时又需要追加一个函数,那么即时运行这个新追加的函数——不得不说,为了使用的便利,这个模式变得有点难以理解。实现起来就是在add()的时候判断是否是auto模型,如果是auto模型,则执行这个函数。 但是,我们需要在第一次fire()之后才自动执行,没有fire()过的Callbacks并不该被自动执行,并且,每次自动执行后,还需要把最后一次使用的参数传递传递给这个自动执行的函数。
或许大家会想到如下代码:
复制代码 代码如下:
(function (window, undefined) {
var Callbacks = function (once, auto) {
var list = [],
fired,
lastData;//保存最后一次执行的参数
return {
add: function (fn) {
if (list) {
list.push(fn);
// — 自动执行模式
//最后一次使用的参数传递过去,这里丢失了Context(上下文)
//为了不让这里丢失上下文,我们或许还需要声明一个变量保存最后一次使用的Context
if (auto) this.fire(lastData);
}
return this;
},
fireWith: function (context, data) {
if (list) {
lastData = data;// — 记录最后一次使用的参数
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
if (list[i].apply(context, data) === false)
break;
}
}
if (once) list = [];
return this;
}
//部分代码省略
};
};
//注册到window下
window.Callbacks = Callbacks;
}(window));
但是在jQuery里采用了更奇妙的用法,获取jQuery作者也自豪这种用法,所以命名这个模型为memory——就是让上面的变量auto不仅仅表示当前是auto执行模式,并且作为最后一次参数的容器,它既表示了auto,也表示了memory。(下面的代码非jQuery是根据jQuery代码思路而写,非源码):
复制代码 代码如下:
(function (window, undefined) {
var Callbacks = function (auto) {
var list = [],
fired,
memory,//主演在这里,就是memory
coreFire = function (data) {
//真正的触发函数方法
if (list) {
//&&表达式妙用
memory = auto && data;//记录最后一次的参数,如果不是auto模式则不会记录这个参数
//如果是auto模式,那么这个auto将不会为false,它会是一个数组
fired = true;
for (var i = 0, len = list.length; i < len; i++) {
if (list[i].apply(data[0], data[1]) === false)
break;
}
}
};
return {
add: function (fn) {
if (list) {
//添加一个回调函数
list.push(fn);
//自动执行模式,注意如果auto模型
//memory是在coreFire()里赋值的,默认是false
if (memory) coreFire(auto);
}
//支持链式回调
return this;
},
fireWith: function (context, data) {
if (once) list = [];
//这里调用coreFire,把参数转换为数组了
coreFire([context, data]);
return this;
}
/*部分代码省略*/
};
};
window.Callbacks = Callbacks;
}(window));
我们在上一个auto实现的代码中看到我们丢失了Context,jQuery早在fireWith()中修复了这个bug——在fireWith()中修复参数。jQuery把fireWith()中本来应该执行函数的逻辑给抽离出来,我们暂时将它命名为coreFire(),在原fireWith()中,将参数拼接成一个数组:第一个参数表示上下文,第二个参数表示传递进来的参数。然后执行coreFire()。
在add()的时候,jQuery并没有给变量auto(memory)赋值,而是选择在coreFire()中给auto(memory)赋值,这样就保证了第一次fire()之后才会开启自动执行。
按照上面所说,coreFire()接收的参数其实是一个数组,第一个参数是上下文,第二个参数是外面传递进来的参数。同时把这个数组赋值给auto(memory),这样,变量auto(是否自动执行模式)的定义就变成了memory(记忆最后一次传递的参数)。
真是一石二鸟的神思路,神想法,不得不点赞。我定义这个为auto是因为它的本身就是一个自动执行的模型,顺便保存了最后一次fire()的参数,而jQuery定义为memory或许也是作者感叹这里的鬼斧神工吧。
至于once&auto就是把这两个代码揉合到一起而已,只需要在coreFire()里判定如果是auto模式,那么就把list重置为一个新的数组,否则直接设置为undefined即可。
源码
这份代码是自己对应jQuery手写的一份,将一些jQuery公有的函数都写了进来,并非代码片段,所以可以直接引用运行。
复制代码 代码如下:
(function (window, undefined) {
/*
* 一个回调函数工具对象,注意这个工作对象工作完成之后就会清空数组:
* 提供一组普通的API,但它有如下工作模型 -
* once - 单次执行模型:每次工作一次,后续不再工作
* auto - 自动执行模型:每添加一个回调函数,自动执行现有的回调函数集合里的所有回调函数,并将本次的参数传递给所有的回调函数
*
*/
//工具函数
var isIndexOf = Array.prototype.indexOf, //Es6
toString = Object.prototype.toString, //缓存toString方法
toSlice = Array.prototype.slice, //缓存slice方法
isFunction = (function () { //判定一个对象是否是Function
return "object" === typeof document.getElementById "" + fn);
} catch (x) {
return false
}
} :
isFunction = function (fn) { return toString.call(fn) === '[object Function]'; };
})(),
each = function () { //循环遍历方法
//第一个参数表示要循环的数组,第二个参数是每次循环执行的函数
if (arguments.length < 2 || !isFunction(arguments[1])) return;
//为什么slice无效??
var list = toSlice.call(arguments[0]),
fn = arguments[1],
item;
while ((item = list.shift())) {//没有直接判定length,加速
// 为什么这里用call就可以,而apply就不行?
//搞定 - apply的第二个参数必须是一个array对象(没有验证array-like是否可以,而call没有这个要求)
//apply是这样描述的:如果 argArray(第二个参数) 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
fn.call(window, item);
}
},
inArray = function () { //检测数组中是否包含某项,返回该项索引
//预编译
return isIndexOf "https://github.com/linkFly6/linkfly.so/blob/master/LinkFLy/jQuery/jQuery.LinkFLy/Callbacks.js">https://github.com/linkFly6/linkfly.so/blob/master/LinkFLy/jQuery/jQuery.LinkFLy/Callbacks.js
以上就是本文给大家分享的全部内容了,希望大家能够喜欢。
jQuery,Callbacks
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 雨林唱片《赏》新曲+精选集SACD版[ISO][2.3G]
- 罗大佑与OK男女合唱团.1995-再会吧!素兰【音乐工厂】【WAV+CUE】
- 草蜢.1993-宝贝对不起(国)【宝丽金】【WAV+CUE】
- 杨培安.2009-抒·情(EP)【擎天娱乐】【WAV+CUE】
- 周慧敏《EndlessDream》[WAV+CUE]
- 彭芳《纯色角3》2007[WAV+CUE]
- 江志丰2008-今生为你[豪记][WAV+CUE]
- 罗大佑1994《恋曲2000》音乐工厂[WAV+CUE][1G]
- 群星《一首歌一个故事》赵英俊某些作品重唱企划[FLAC分轨][1G]
- 群星《网易云英文歌曲播放量TOP100》[MP3][1G]
- 方大同.2024-梦想家TheDreamer【赋音乐】【FLAC分轨】
- 李慧珍.2007-爱死了【华谊兄弟】【WAV+CUE】
- 王大文.2019-国际太空站【环球】【FLAC分轨】
- 群星《2022超好听的十倍音质网络歌曲(163)》U盘音乐[WAV分轨][1.1G]
- 童丽《啼笑姻缘》头版限量编号24K金碟[低速原抓WAV+CUE][1.1G]