在jquery中,链式编程指的是对同一个元素一直进行函数操作;链式编程是将多行代码合并成一行代码,每一个合并的方法返回的结果是元素对象才可以进行链式编程,语法为“元素对象.方法().方法().方法()…;”。
实现链式编程的核心,是对象中的每一个方法都会返回当前对象。
链式编程:多行代码合并成一行代码,前提要认清此行代码返回的是不是对象,是对象才能进行链式编程
链式编程:对象.方法().方法().方法();…
//这是普通的事件绑定
$("button").click(function() {
console.log("1")
})
$("button").mouseenter(function() {
console.log("2")
})
$("button").mouseleave(function() {
console.log("3")
})
//与上文功能相同的链式编程
$("button").click(function() {
console.log("1")
}).mouseenter(function() {
console.log("2")
}).mouseleave(function() {
console.log("3")
})
实现链式编程的核心,是函数调用结束之后返回的this对象,指的是当前调用者。这里的 ( " b u t t o n " ) . c l i c k ( f u n c t i o n ( ) ) 调 用 结 束 之 后 , 返 回 t h i s 对 象 , 它 相 当 于 ("button").click(function(){})调用结束之后,返回this对象,它相当于 ("button").click(function())调用结束之后,返回this对象,它相当于(“button”),这样和后面的合在一起就实现了$(“button”).mouseenter(function() {})的函数调用,以上就是链式编程实现的一般步骤。
//普通写法
$("#btn1").on("click", function () {
console.log("点击事件")
});
$("#btn1").on("mouseenter", function () {
//注意这里的on函数的链式编程
console.log("鼠标聚焦事件")
});
$("#btn1").on("mouseleave", function () {
//注意这里的on函数的链式编程
console.log("鼠标失焦事件")
})
//链式编程
$("#btn1").on("click", function () {
console.log("点击事件")
}).on("mouseenter", function () {
//注意这里的on函数的链式编程
console.log("鼠标聚焦事件")
}).on("mouseleave", function () {
//注意这里的on函数的链式编程
console.log("鼠标失焦事件")
})
这里的on函数链式编程,函数调用结束之后,会隐式返回this关键字,表示当前调用的对象,这里第一个on函数调用结束之后,返回的this关键字表示的就是$(“#btn1”),所有之后再加上on函数是顺理成章的事情。
//普通写法
$("button").bind({
"click": function () {
console.log("点击事件")
}
});
$("button").bind({
"mouseenter": function () {
console.log("鼠标聚焦事件")
}
});
$("button").bind({
"mouseleave": function () {
console.log("鼠标离焦事件")
}
});
//链式编程
$("button").bind({
"click": function () {
console.log("点击事件")
},
"mouseenter": function () {
console.log("鼠标聚焦事件")
},
"mouseleave": function () {
console.log("鼠标离焦事件")
}
});
这里的bind函数链式编程,是将多个参数同时放在bind函数中,这是因为每个参数是以字典的形式存储,有着相同的格式,所以才可以同时作为并列参数放在bind函数中,需要记住这样的格式。
//混合使用
$("button").bind({
"click": function () {
console.log("点击事件")
}
});
$("button").bind({
"mouseenter": function () {
console.log("鼠标聚焦事件")
}
}).mouseleave(function () {
console.log("混合使用的离焦事件")
});
一般的解释:节省代码量,代码看起来更优雅。
例如如果没有链式,那么你可能需要这样写代码:
document.getElementById("ele").dosomething();
document.getElementById("ele").dootherthing();
这个代码中调用了两次document.getElementById来获取DOM树的元素,这样消耗比较大,而且要写两行,而链式只要写一行,节省了代码……
但我们也可以用缓存元素啊。比如:
var ele = document.getElementById("ele");
ele.dosomething();
ele.dootherthing();
而且两行并没有比一行多多少代码,甚至相应的封装反而使得代码更多了。
最糟糕的是所有对象的方法返回的都是对象本身,也就是说没有返回值,这不一定在任何环境下都适合。
举个例子,我们想弄一个超大整数BigInteger(意思是如果用Javascript的Number保存可能会溢出的整数),顺便扩展他的运算方法,会适合用链式操作么?
例如运算31415926535 * 4 - 271828182,如果设计成链式风格的方法可能会是这样的:
var result = (new BigInteger("31415926535")).multiply(new BigInteger("4")).subtract(new BigInteger("271828182")).val();
console.log("result == " + result);
这看起来似乎也很优雅,但是如果我们想要中间的结果怎么办呢?或许会写成这样:
var bigInteger = new BigInteger("31415926535");
var result1 = bigInteger.multiply(new BigInteger("4")).val();
var result2 = bigInteger.subtract(new BigInteger("271828182")).val();
console.log("result1 == " + result1 + ", result2 == " + result2);
这似乎一点也不优雅了,和不用链式操作没啥不同嘛!
那么如果要求是原来的BigInteger不能改变呢?好吧,链式操作似乎不能满足这个需求了。
jQuery专注于DOM对象操作,而DOM的操作会在页面上体现,不需要在Javascript中通过返回值来表示,但计算操作却不一样,我们很可能需要通过Javascript返回中间过程值另作他用。
在设计的时候,我们需要考虑链式带来的好处和坏处,因为别人用了链式,所以就用链式,可能并不是一个很好的方案。
那么到底为什么要用链式操作呢?
为了更好的异步体验
Javascript是无阻塞语言,所以他不是没阻塞,而是不能阻塞,所以他需要通过事件来驱动,异步来完成一些本需要阻塞进程的操作。
但是异步编程是一种令人疯狂的东西……运行时候是分离的倒不要紧,但是编写代码时候也是分离的就……
常见的异步编程模型有哪些呢?
function f(num, callback){
if(num<0) {
alert("调用低层函数处理!");
alert("分数不能为负,输入错误!");
}else if(num==0){
alert("调用低层函数处理!");
alert("该学生可能未参加考试!");
}else{
alert("调用高层函数处理!");
setTimeout(function(){callback();}, 1000);
}
}
这里callback则是回调函数。可以发现只有当num为非负数时候callback才会调用。
但是问题,如果我们不看函数内部,我们并不知道callback会几时调用,在什么情况下调用,代码间产生了一定耦合,流程上也会产生一定的混乱。
虽然回调函数是一种简单而易于部署的实现异步的方法,但从编程体验来说它却不够好。
function EventTarget(){
this.handlers = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addHandler: function(type, handler){
this.handlers[type] = [];
},
fire: function(){
if(!event.target){
event.target = this;
}
if(this.handlers[event.type instanceof Array]){
var handlers = this.handlers[event.type];
for(var i = 0, len = handlers.length, i < len; i++){
handlers[i](event);
}
}
},
removeHandler: function(type, handler){
if(this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for(var i = 0, le = handlers.length; i < len; i++){
if(handlers[i] === handler){
break;
}
}
handlers.splice(i, 1);
}
}
};
上面是《JavaScript高级程序设计》中的自定义事件实现。于是我们就可以通过addHandler来绑定事件处理函数,用fire来触发事件,用removeHandler来删除事件处理函数。
虽然通过事件解耦了,但流程顺序更加混乱了。
个人觉得链式操作最值得称赞的还是其解决了异步编程模型的执行流程不清晰的问题。jQuery中 ( d o c u m e n t ) . r e a d y 就 非 常 好 的 阐 释 了 这 一 理 念 。 D O M C o t e n t L o a d e d 是 一 个 事 件 , 在 D O M 并 未 加 载 前 , j Q u e r y 的 大 部 分 操 作 都 不 会 奏 效 , 但 j Q u e r y 的 设 计 者 并 没 有 把 他 当 成 事 件 一 样 来 处 理 , 而 是 转 成 一 种 “ 选 其 对 象 , 对 其 操 作 ” 的 思 路 。 (document).ready就非常好的阐释了这一理念。DOMCotentLoaded是一个事件,在DOM并未加载前,jQuery的大部分操作都不会奏效,但jQuery的设计者并没有把他当成事件一样来处理,而是转成一种“选其对象,对其操作”的思路。 (document).ready就非常好的阐释了这一理念。DOMCotentLoaded是一个事件,在DOM并未加载前,jQuery的大部分操作都不会奏效,但jQuery的设计者并没有把他当成事件一样来处理,而是转成一种“选其对象,对其操作”的思路。选择了document对象,ready是其方法进行操作。这样子流程问题就非常清晰了,在链条越后位置的方法就越后执行。
(function(){
var isReady=false; //判断onDOMReady方法是否已经被执行过
var readyList= [];//把需要执行的方法先暂存在这个数组里
var timer;//定时器句柄
ready=function(fn) {
if (isReady )
fn.call( document);
else
readyList.push( function() { return fn.call(this);});
return this;
}
var onDOMReady=function(){
for(var i=0;i<readyList.length;i++){
readyList[i].apply(document);
}
readyList = null;
}
var bindReady = function(evt){
if(isReady) return;
isReady=true;
onDOMReady.call(window);
if(document.removeEventListener){
document.removeEventListener("DOMContentLoaded", bindReady, false);
}else if(document.attachEvent){
document.detachEvent("onreadystatechange", bindReady);
if(window == window.top){
clearInterval(timer);
timer = null;
}
}
};
if(document.addEventListener){
document.addEventListener("DOMContentLoaded", bindReady, false);
}else if(document.attachEvent){
document.attachEvent("onreadystatechange", function(){
if((/loaded|complete/).test(document.readyState))
bindReady();
});
if(window == window.top){
timer = setInterval(function(){
try{
isReady||document.documentElement.doScroll('left');//在IE下用能否执行doScroll判断dom是否加载完毕
}catch(e){
return;
}
bindReady();
},5);
}
}
})();
上面的代码不能用$(document).ready,而应该是window.ready。
CommonJS中的异步编程模型也延续了这一想法,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。
所以我们可以这样写:
f1().then(f2).then(f3);