最近jQuery1.7中统一了bind,live和delegate机制,天下一统,只有on/off.
8. 动画队列:全局时钟协调
抛开jQuery的实现不谈,先考虑一下如果我们要实现界面上的动画效果,到底需要做些什么? 比如我们希望将一个div的宽度在1秒钟之内从100px增加到200px. 很容易想见,在一段时间内我们需要不时的去调整一下div的宽度,[同时]我们还需要执行其他代码. 与一般的函数调用不同的是,发出动画指令之后,我们不能期待立刻得到想要的结果,而且我们不能原地等待结果的到来. 动画的复杂性就在于:一次性表达之后要在一段时间内执行,而且有多条逻辑上的执行路径要同时展开,如何协调?
伟大的艾萨克.牛顿爵士在《自然哲学的数学原理》中写道:"绝对的、真正的和数学的时间自身在流逝着". 所有的事件可以在时间轴上对齐,这就是它们内在的协调性. 因此为了从步骤A1执行到A5,同时将步骤B1执行到B5,我们只需要在t1时刻执行[A1,B1],在t2时刻执行[A2,B2],依此类推.
t1 | t2 | t3 | t4 | t5 ...
A1 | A2 | A3 | A4 | A5 ...
B1 | B2 | B3 | B4 | B5 ...
具体的一种实现形式可以是
A. 对每个动画,将其分装为一个Animation对象,内部分成多个步骤.
animation = new Animation(div,"width",100,200,1000,
负责步骤切分的插值函数,动画执行完毕时的回调函数);
B. 在全局管理器中注册动画对象
timerFuncs.add(animation);
C. 在全局时钟的每一个触发时刻,将每个注册的执行序列推进一步,如果已经结束,则从全局管理器中删除.
解决了原理问题,再来看看表达问题,怎样设计接口函数才能够以最紧凑形式表达我们的意图? 我们经常需要面临的实际问题:
A. 有多个元素要执行类似的动画
B. 每个元素有多个属性要同时变化
C. 执行完一个动画之后开始另一个动画
jQuery对这些问题的解答可以说是榨尽了js语法表达力的最后一点剩余价值.
函数,因此alert("y")
$(this).dequeue();
alert('x');
})
.queue(function(){
alert("y");
// 如果不主动dequeue,队列执行就中断了,不会
自动继续下去.
$(this).dequeue();
});
A. 利用jQuery内置的selector机制自然表达对一个集合的处理.
B. 使用Map表达多个属性变化
C. 利用微格式表达领域特定的差量概念. '+=200px'表示在现有值的基础上增加200px
D. 利用函数调用的顺序自动定义animation执行的顺序: 在后面追加到执行队列中的动画自然要等前面的动画完全执行完毕之后再启动.
jQuery动画队列的实现细节大概如下所示,
A. animate函数实际是调用queue(function(){执行结束时需要调用dequeue,否则不会驱动下一个方法})
queue函数执行时,如果是fx队列,并且当前没有正在运行动画(如果连续调用两次animate,第二次的执行函数将在队列中等待),则会自动触发dequeue操作,驱动队列运行.
如果是fx队列,dequeue的时候会自动在队列顶端加入"inprogress"字符串,表示将要执行的是动画.
B. 针对每一个属性,创建一个jQuery.fx对象。然后调用fx.custom函数(相当于start)来启动动画。
C. custom函数中将fx.step函数注册到全局的timerFuncs中,然后试图启动一个全局的timer.
timerId = setInterval( fx.tick,fx.interval );
D. 静态的tick函数中将依次调用各个fx的step函数。step函数中通过easing计算属性的当前值,然后调用fx的update来更新属性。
E. fx的step函数中判断如果所有属性变化都已完成,则调用dequeue来驱动下一个方法。
很有意思的是,jQuery的实现代码中明显有很多是接力触发代码: 如果需要执行下一个动画就取出执行,如果需要启动timer就启动timer等. 这是因为js程序是单线程的,真正的执行路径只有一条,为了保证执行线索不中断,函数们不得不互相帮助一下. 可以想见,如果程序内部具有多个执行引擎,甚至无限多的执行引擎,那么程序的面貌就会发生本质性的改变. 而在这种情形下,递归相对于循环而言会成为更自然的描述.
9. promise模式:因果关系的识别
现实中,总有那么多时间线在独立的演化着,人与物在时空中交错,却没有发生因果. 软件中,函数们在源代码中排着队,难免会产生一些疑问,凭什么排在前面的要先执行? 难道没有它就没有我? 让全宇宙喊着1,2,3齐步前进,从上帝的角度看,大概是管理难度过大了,于是便有了相对论. 如果相互之间没有交换信息,没有产生相互依赖,那么在某个坐标系中顺序发生的事件,在另外一个坐标系中看来,就可能是颠倒顺序的. 程序员依葫芦画瓢,便发明了promise模式.
promise与future模式基本上是一回事,我们先来看一下java中熟悉的future模式.
发出函数调用仅仅意味着一件事情发生过,并不必然意味着调用者需要了解事情最终的结果. 函数立刻返回的只是一个将在未来兑现的承诺(Future类型),实际上也就是某种句柄. 句柄被传来传去,中间转手的代码对实际结果是什么,是否已经返回漠不关心. 直到一段代码需要依赖调用返回的结果,因此它打开future,查看了一下. 如果实际结果已经返回,则future.get()立刻返回实际结果,否则将会阻塞当前的执行路径,直到结果返回为止. 此后再调用future.get()总是立刻返回,因为因果关系已经被建立,[结果返回]这一事件必然在此之前发生,不会再发生变化.
future模式一般是外部对象主动查看future的返回值,而promise模式则是由外部对象在promise上注册回调函数.
function showDiv(){
var dfd = $.Deferred();
$('#foo').fadeIn( 1000,dfd.resolve );
return dfd.promise();
}
$.when( getData(),showDiv() )
.then(function( ajaxResult,ignoreResultFromShowDiv ){
console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');
// 'ajaxResult' is the server's response
});
jQuery引入Deferred结构,根据promise模式对ajax,queue,document.ready等进行了重构,统一了异步执行机制. then(onDone,onFail)将向promise中追加回调函数,如果调用成功完成(resolve),则回调函数onDone将被执行,而如果调用失败(reject),则onFail将被执行. when可以等待在多个promise对象上. promise巧妙的地方是异步执行已经开始之后甚至已经结束之后,仍然可以注册回调函数
someObj.done(callback).sendRequest() vs. someObj.sendRequest().done(callback)
callback函数在发出异步调用之前注册或者在发出异步调用之后注册是完全等价的,这揭示出程序表达永远不是完全精确的,总存在着内在的变化维度. 如果能有效利用这一内在的可变性,则可以极大提升并发程序的性能.
promise模式的具体实现很简单. jQuery._Deferred定义了一个函数队列,它的作用有以下几点:
A. 保存回调函数。
B. 在resolve或者reject的时刻把保存着的函数全部执行掉。
C. 已经执行之后,再增加的函数会被立刻执行。
一些专门面向分布式计算或者并行计算的语言会在语言级别内置promise模式,比如E语言.
<div class="codetitle"><a style="CURSOR: pointer" data="54685" class="copybut" id="copybut54685" onclick="doCopy('code54685')"> 代码如下:
<div class="codebody" id="code54685">def carPromise := carMaker <- produce("Mercedes");
def temperaturePromise := carPromise <- getEngineTemperature()
...
when (temperaturePromise) -> done(temperature) {
println(
The temperature of the car engine is: $temperature
)
} catch e {
println(
Could not get engine temperature,error: $e
)
}
在E语言中,<-是eventually运算符,表示最终会执行,但不一定是现在. 而普通的car.moveTo(2,3)表示立刻执行得到结果. 编译器负责识别所有的promise依赖,并
自动实现调度.
10. extend: 继承不是必须的
js是基于原型的语言,并没有内置的继承机制,这一直让很多深受传统面向对象教育的同学们耿耿于怀. 但继承一定是必须的吗? 它到底能够给我们带来什么? 最纯朴的回答是: 代码重用. 那么,我们首先来分析一下继承作为代码重用手段的潜力.
曾经有个概念叫做"多重继承",它是继承概念的超级赛亚人版,很遗憾后来被诊断为存在着先天缺陷,以致于出现了一种对于继承概念的解读: 继承就是"is a"关系,一个派生对象"is a"很多基类,必然会出现精神分裂,所以多重继承是不好的.
如果D类从A,B两个基类继承,而A和B类中都实现了同一个函数f,那么D类中的f到底是A中的f还是B中的f,抑或是A中的f+B中的f呢? 这一困境的出现实际上源于D的基类A和B是并列关系,它们满足交换律和结合律,毕竟,在概念层面上我们可能难以认可两个任意概念之间会出现从属关系. 但如果我们放松一些概念层面的要求,更多的从操作层面考虑一下代码重用问题,可以简单的认为B在A的基础上进行操作,那么就可以得到一个线性化的结果. 也就是说,放弃A和B之间的交换律只保留结合律,extends A,B 与 extends B,A 会是两个不同的结果,不再存在诠释上的二义性. scala语言中的所谓trait(特性)机制实际上采用的就是这一策略.
面向对象技术发明很久之后,出现了所谓的面向方面编程(AOP),它与OOP不同,是代码结构空间中的定位与修改技术. AOP的眼中只有类与方法,不知道什么叫做意义. AOP也提供了一种类似多重继承的代码重用手段,那就是mixin. 对象被看作是可以被打开,然后任意修改的Map,一组成员变量与方法就被直接注射到对象体内,直接改变了它的行为.
prototype.js库引入了extend函数,
就是Map之间的一个覆盖运算,但很管用,在jQuery库中也得到了延用. 这个操作类似于mixin,在jQuery中是代码重用的主要技术手段---没有继承也没什么大不了的.
11. 名称映射: 一切都是数据
代码好不好,循环判断必须少. 循环和判断语句是程序的基本组成部分,但是优良的代码库中却往往找不到它们的踪影,因为这些语句的交织会模糊系统的逻辑主线,使我们的思想迷失在疲于奔命的代码追踪中. jQuery本身通过each,extend等函数已经极大减少了对循环语句的需求,对于判断语句,则主要是通过映射表来处理. 例如,jQuery的val()函数需要针对不同标签进行不同的处理,因此定义一个以tagName为key的函数映射表
这样在程序中就不需要到处写
可以统一处理
映射表将函数作为普通数据来管理,在动态语言中有着广泛的应用. 特别是,对象本身就是函数和变量的容器,可以被看作是映射表. jQuery中大量使用的一个技巧就是利用名称映射来动态生成代码,形成一种类似模板的机制. 例如为了实现myWidth和myHeight两个非常类似的函数,我们不需要
<div class="codetitle"><a style="CURSOR: pointer" data="48277" class="copybut" id="copybut48277" onclick="doCopy('code48277')"> 代码如下:
<div class="codebody" id="code48277">jQuery.fn.myWidth = function(){
return parseInt(this.style.width,10) + 10;
}
jQuery.fn.myHeight = function(){
return parseInt(this.style.height,10) + 10;
}
而可以选择动态
生成
<div class="codetitle">
<a style="CURSOR: pointer" data="66435" class="copybut" id="copybut66435" onclick="doCopy('code66435')"> 代码如下:<div class="codebody" id="code66435">jQuery.each(['Width','Height'],function(name){
jQuery.fn['my'+name] = function(){
return parseInt(this.style[name.toLowerCase()],10) + 10;
}
});
12. 插件机制:其实我很简单
jQuery所谓的插件其实就是$.fn上增加的函数,那这个fn是什么东西?
//
调用jQuery()就是相当于new init(),而init的prototype就是jQuery的prototype
jQuery.fn.init.prototype = jQuery.fn;
// 这里返回的jQuery对象只具备最基本的功能,下面就是一系列的extend
return jQuery;
})();
...
// 将jQuery暴露为全局对象
window.jQuery = window.$ = jQuery;
})(window);
显然,$.fn其实就是jQuery.prototype的简写.
无状态的插件仅仅就是一个函数,非常简单.
<div class="codetitle"><a style="CURSOR: pointer" data="75384" class="copybut" id="copybut75384" onclick="doCopy('code75384')"> 代码如下:
<div class="codebody" id="code75384">// 定义
插件
(function($){
$.fn.hoverClass = function(c) {
return this.hover(
function() { $(this).toggleClass(c); }
);
};
})(jQuery);
// 使用插件
$('li').hoverClass('hover');