在编写jQuery动画时,通过回调函数进行动画队列的编排,但当回调过多,往往会出现这样的代码:
1 $(".box1").fadeIn(1000,function(){ 2 $(".box2").fadeIn(1000,function(){ 3 $(".box3").hide(1000,function(){ 4 // todo xxx 5 }) 6 }) 7 })
那么是否可以将这种层次的回调以链式的写法完成呢?
如:f(fn1).f(fn2).f(fn3)... 表示fn1执行完成后fn2,fn2执行完成后执行fn3,...
下面逐步介绍实现方式:
1、确定结构:
为实现链式调用,需要f()的返回值一个包含其本身的对象
function f(n){ var obj = {}; obj.f = f; return obj; }
这样就构造好了链式调用的基本结构。
尝试加入参数:
function f(n){ console.log(n); var obj = {}; obj.f = function(m){return f(m);}; return obj; }
与我们预想的结果相同,但如结果所见,f调用()会立即将参数代入执行。现在增加一个需求,希望f().f().f()...仅表示一个序列,真正的执行需要我下达其他命令后才发生。于是我们需要在f()的obj中加入一个函数run(),此函数用来下达执行指令,修改代码如下:
1 function f(n){ 2 var obj = {}; 3 obj.f = function(m){return f(m);}; 4 obj.run = function(){ 5 console.log(n) 6 } 7 return obj; 8 }
可以看到,在我们原先的代码并没有输出结果,而当我们执行run()后会怎样呢?
可以看到仅输出了第三次结果,这是因为run()并没有保存前两次f()参数的值,我们期望的将三次f()一并执行,实际上仅仅执行了第三次。那么如何解决这个问题呢,换句话说:如何在run()中保存所有之前获得的f()的参数呢?
2、保存参数:
我们知道,函数通过参数获得函数外部变量的值,这里我们就通过这种方式保存f()的所有参数,代码修改如下:
1 function f(n,fn){ 2 var obj = {}; 3 obj.arr = fn===undefined?[]:[fn]; 4 obj.arr.push(n); 5 obj.f = function(m){return f(m,n);}; 6 obj.run = function(){ 7 $.each(obj.arr,function(i,fn){ 8 fn(); 9 }); 10 } 11 return obj; 12 }
f()中添加obj.arr数组,用来保存所有传入的参数,而run()统一执行所有传入的参数,f()的第二个参数为上一次传入的参数,这样上一次执行所需的参数就被保存了下来。我们将传入的参数换成函数,用如下代码测试:
可以看到这正是我们想要的结果。
再次向后追加,希望输出1 2 3,但实际只输出后两次结果,原因在于f()的第二个参数仅保存了上一次传入的参数,而不是前几次的
f()的第二个参数为数组,用来保存前几次已传入的参数,修改代码如下:
function f(n,ar){ var obj = {}; obj.arr = ar===undefined?[]:ar; obj.arr.push(n); obj.f = function(m){return f(m,obj.arr);}; obj.run = function(){ $.each(obj.arr,function(i,fn){ fn(); }); } return obj; }
再次执行之前的代码,已符合预期。
3、执行回调
f( function () { $(".box1").fadeIn(1000); } ).f( function () { $(".box2").fadeIn(1000); } ).run();
执行上述代码,我们希望看到的是box1用1秒淡入,这个动画执行完后,box2开始执行1秒淡入,但实际上,这两个动画几乎是同时执行,所以接下来的关键是如何执行回调。修改测试代码如下:
f( function (cb1) { $(".box1").fadeIn(1000,cb1); } ).f( function (cb2) { $(".box2").fadeIn(1000,cb2); } ).run();
我们看到,cb1和cb2分别为box1和box2执行动画的回调函数,也就是说,我们希望达到这样的效果:在cb1中,执行box2的动画,在cb2中执行后面的动画(如果有),即:需要在cb中下达执行下一个f()中函数的指令。而run()仅仅是控制f()当前携带函数的执行,修改代码如下:
function f(n,ar){ var obj = {}; obj.arr = ar===undefined?[]:ar; obj.arr.push(n); obj.f = function(m){return f(m,obj.arr);}; obj.run = function(){ var fn = obj.arr.shift(); if(typeof fn === "function") fn(callback); } function callback(){ obj.run(); } return obj; }
f( function (cb) { $(".box1").fadeIn(1000,cb); } ).f( function (cb) { $(".box2").fadeIn(1000,cb); } ).run();
再次执行以上代码进行测试,实现box1执行完成后执行box2,目标实现