JQUERY教程

当前位置: HTML5技术网 > JQUERY教程 > jQuery 中的编程范式(中)

jQuery 中的编程范式(中)

       5.链式操作: 线性化的逐步细化

        jQuery早期最主要的卖点就是所谓的链式操作(chain)。

  1. $(‘#content’) // 找到content元素
  2.     .find(‘h3′) // 选择所有后代h3节点
  3.     .eq(2)      // 过滤集合, 保留第三个元素
  4.         .html(‘改变第三个h3的文本’)
  5.     .end()      // 返回上一级的h3集合
  6.     .eq(0)
  7.         .html(‘改变第一个h3的文本’);
复制代码
        在一般的命令式语言中, 我们总需要在重重嵌套循环中过滤数据, 实际操作数据的代码与定位数据的代码纠缠在一起。 而jQuery采用先构造集合然后再应用函数于集合的方式实现两种逻辑的解耦, 实现嵌套结构的线性化。 实际上, 我们并不需要借助过程化的思想就可以很直观的理解一个集合, 例如 $(‘div。my input:checked’)可以看作是一种直接的描述,而不是对过程行为的跟踪。

        循环意味着我们的思维处于一种反复回绕的状态, 而线性化之后则沿着一个方向直线前进, 极大减轻了思维负担, 提高了代码的可组合性。 为了减少调用链的中断, jQuery发明了一个绝妙的主意: jQuery包装对象本身类似数组(集合)。 集合可以映射到新的集合, 集合可以限制到自己的子集合,调用的发起者是集合,返回结果也是集合,集合可以发生结构上的某种变化但它还是集合, 集合是某种概念上的不动点,这是从函数式语言中吸取的设计思想。集合操作是太常见的操作, 在java中我们很容易发现大量所谓的封装函数其实就是在封装一些集合遍历操作, 而在jQuery中集合操作因为太直白而不需要封装。

        链式调用意味着我们始终拥有一个“当前”对象,所有的操作都是针对这一当前对象进行。这对应于如下公式

  1. x += dx
复制代码
        调用链的每一步都是对当前对象的增量描述,是针对最终目标的逐步细化过程。Witrix平台中对这一思想也有着广泛的应用。特别是为了实现平台机制与业务代码的融合,平台会提供对象(容器)的缺省内容,而业务代码可以在此基础上进行逐步细化的修正,包括取消缺省的设置等。

        话说回来, 虽然表面上jQuery的链式调用很简单, 内部实现的时候却必须自己多写一层循环, 因为编译器并不知道”自动应用于集合中每个元素”这回事。

  1. $.fn['someFunc'] = function(){
  2.     return this.each(function(){
  3.       jQuery.someFunc(this,…);
  4.     }
  5.   }
复制代码

        6.data: 统一数据管理

        作为一个js库,它必须解决的一个大问题就是js对象与DOM节点之间的状态关联与协同管理问题。有些js库选择以js对象为主,在js对象的成员变量中保存DOM节点指针,访问时总是以js对象为入口点,通过js函数间接操作DOM对象。在这种封装下,DOM节点其实只是作为界面展现的一种底层“汇编” 而已。jQuery的选择与Witrix平台类似,都是以HTML自身结构为基础,通过js增强(enhance)DOM节点的功能,将它提升为一个具有复杂行为的扩展对象。这里的思想是非侵入式设计(non-intrusive)和优雅退化机制(graceful degradation)。语义结构在基础的HTML层面是完整的,js的作用是增强了交互行为,控制了展现形式。

        如果每次我们都通过$(‘#my’)的方式来访问相应的包装对象,那么一些需要长期保持的状态变量保存在什么地方呢?jQuery提供了一个统一的全局数据管理机制。

  1. //获取数据
  2. $(‘#my’).data(‘myAttr’)
  3. //设置数据
  4. $(‘#my’).data(‘myAttr’,3);
复制代码

        这一机制自然融合了对HTML5的data属性的处理

  1. <input id=”my” data-my-attr=”4″ … />
复制代码

        通过 $(‘#my’)。data(‘myAttr’)将可以读取到HTML中设置的数据。

        第一次访问data时,jQuery将为DOM节点分配一个唯一的uuid, 然后设置在DOM节点的一个特定的expando属性上, jQuery保证这个uuid在本页面中不重复。

  1. elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
复制代码
        以上代码可以同时处理DOM节点和纯js对象的情况。如果是js对象,则data直接放置在js对象自身中,而如果是DOM节点,则通过cache统一管理。

        因为所有的数据都是通过data机制统一管理的,特别是包括所有事件监听函数(data。events),因此jQuery可以安全的实现资源管理。在 clone节点的时候,可以自动clone其相关的事件监听函数。而当DOM节点的内容被替换或者DOM节点被销毁的时候,jQuery也可以自动解除事件监听函数, 并安全的释放相关的js数据。

       7.event:统一事件模型

        “事件沿着对象树传播”这一图景是面向对象界面编程模型的精髓所在。对象的复合构成对界面结构的一个稳定的描述,事件不断在对象树的某个节点发生,并通过冒泡机制向上传播。对象树很自然的成为一个控制结构,我们可以在父节点上监听所有子节点上的事件,而不用明确与每一个子节点建立关联。

        jQuery除了为不同浏览器的事件模型建立了统一抽象之外,主要做了如下增强:

        A.增加了自定制事件(custom)机制。 事件的传播机制与事件内容本身原则上是无关的, 因此自定制事件完全可以和浏览器内置事件通过同一条处理路径, 采用同样的监听方式。 使用自定制事件可以增强代码的内聚性, 减少代码耦合。 例如如果没有自定制事件, 关联代码往往需要直接操作相关的对象

  1. $(‘.switch, .clapper’).click(function() {
  2.     var $light = $(this).parent().find(‘.lightbulb’);
  3.     if ($light.hasClass(‘on’)) {
  4.         $light.removeClass(‘on’).addClass(‘off’);
  5.     } else {
  6.         $light.removeClass(‘off’).addClass(‘on’);
  7.     }
  8.   });
复制代码
        而如果使用自定制事件,则表达的语义更加内敛明确,

  1. $(‘.switch, .clapper’).click(function() {
  2.   $(this).parent().find(‘.lightbulb’).trigger(‘changeState’);
  3. });
复制代码
        B.增加了对动态创建节点的事件监听。bind函数只能将监听函数注册到已经存在的DOM节点上。 例如

  1. $(‘li.trigger’).bind(‘click’,function(){}}
复制代码

如果调用bind之后,新建了另一个li节点,则该节点的click事件不会被监听。

        jQuery的delegate机制可以将监听函数注册到父节点上, 子节点上触发的事件会根据selector被自动派发到相应的handlerFn上。 这样一来现在注册就可以监听未来创建的节点。

  1. $(‘#myList’).delegate(‘li.trigger’, ’click’, handlerFn);
复制代码
        最近jQuery1.7中统一了bind, live和delegate机制, 天下一统, 只有on/off。

  1. $(‘li.trigger’).on(‘click’, handlerFn);  // 相当于bind
  2. $(‘#myList’).on(‘click’, ’li.trigger’, handlerFn);  // 相当于delegate
复制代码

        8. 动画队列:全局时钟协调

        抛开jQuery的实现不谈, 先考虑一下如果我们要实现界面上的动画效果, 到底需要做些什么? 比如我们希望将一个div的宽度在1秒钟之内从100px增加到200px。很容易想见, 在一段时间内我们需要不时的去调整一下div的宽度, [同时]我们还需要执行其他代码. 与一般的函数调用不同的是, 发出动画指令之后, 我们不能期待立刻得到想要的结果, 而且我们不能原地等待结果的到来. 动画的复杂性就在于:一次性表达之后要在一段时间内执行,而且有多条逻辑上的执行路径要同时展开, 如何协调?

        伟大的艾萨克.牛顿爵士在《自然哲学的数学原理》中写道:”绝对的、真正的和数学的时间自身在流逝着”.所有的事件可以在时间轴上对齐, 这就是它们内在的协调性.因此为了从步骤A1执行到A5, 同时将步骤B1执行到B5, 我们只需要在t1时刻执行[A1, B1], 在t2时刻执行[A2,B2], 依此类推。

  1. t1 | t2 | t3 | t4 | t5 …
  2. A1 | A2 | A3 | A4 | A5 …
  3. B1 | B2 | B3 | B4 | B5 …
复制代码
        具体的一种实现形式可以是

        A. 对每个动画, 将其分装为一个Animation对象, 内部分成多个步骤。

  1. animation = new Animation(div,”width”,100,200,1000,
  2. //负责步骤切分的插值函数,动画执行完毕时的回调函数);
复制代码
        B. 在全局管理器中注册动画对象

  1. timerFuncs.add(animation);
复制代码
        C. 在全局时钟的每一个触发时刻, 将每个注册的执行序列推进一步, 如果已经结束, 则从全局管理器中删除。

  1. for each animation in timerFuncs
  2.    if(!animation.doOneStep())
  3.       timerFuncs.remove(animation)
复制代码
        解决了原理问题,再来看看表达问题, 怎样设计接口函数才能够以最紧凑形式表达我们的意图? 我们经常需要面临的实际问题:

        A. 有多个元素要执行类似的动画

        B. 每个元素有多个属性要同时变化

        C. 执行完一个动画之后开始另一个动画

        jQuery对这些问题的解答可以说是榨尽了js语法表达力的最后一点剩余价值.

  1. $(‘input’)
  2.   .animate({left:’+=200px’,top:’300′},2000)
  3.   .animate({left:’-=200px’,top:20},1000)
  4.   .queue(function(){
  5.     // 这里dequeue将首先执行队列中的后一个函数,因此alert(“y”)
  6.     $(this).dequeue();
  7.     alert(‘x’);
  8.    })
  9.   .queue(function(){
  10.      alert(“y”);
  11.      // 如果不主动dequeue, 队列执行就中断了,不会自动继续下去.
  12.      $(this).dequeue();
  13.    });
复制代码
        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。

  1. 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模式。

  1. futureResult = doSomething();

  2. realResult = futureResult.get();
复制代码
        发出函数调用仅仅意味着一件事情发生过, 并不必然意味着调用者需要了解事情最终的结果。函数立刻返回的只是一个将在未来兑现的承诺(Future类型), 实际上也就是某种句柄。句柄被传来传去, 中间转手的代码对实际结果是什么,是否已经返回漠不关心。直到一段代码需要依赖调用返回的结果, 因此它打开future, 查看了一下。如果实际结果已经返回, 则future.get()立刻返回实际结果, 否则将会阻塞当前的执行路径, 直到结果返回为止。此后再调用future.get()总是立刻返回, 因为因果关系已经被建立, [结果返回]这一事件必然在此之前发生, 不会再发生变化。

        future模式一般是外部对象主动查看future的返回值, 而promise模式则是由外部对象在promise上注册回调函数。

  1. function getData(){
  2. return $.get(‘/foo/’).done(function(){
  3.     console.log(‘Fires after the AJAX request succeeds’);
  4. }).fail(function(){
  5.     console.log(‘Fires after the AJAX request fails’);
  6. });
  7. }

  8. function showDiv(){
  9.   var dfd = $.Deferred();
  10.   $(‘#foo’).fadeIn( 1000, dfd.resolve );
  11.   return dfd.promise();
  12. }

  13. $.when( getData(), showDiv() )
  14.   .then(function( ajaxResult, ignoreResultFromShowDiv ){
  15.       console.log(‘Fires after BOTH showDiv() AND the AJAX request succeed!’);
  16.       // ’ajaxResult’ is the server’s response
  17.   });
复制代码
        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语言。

  1. def carPromise := carMaker <- produce(“Mercedes”);
  2. def temperaturePromise := carPromise <- getEngineTemperature()

  3. when (temperaturePromise) -> done(temperature) {
  4.   println(`The temperature of the car engine is: $temperature`)
  5. } catch e {
  6.   println(`Could not get engine temperature, error: $e`)
  7. }
复制代码
        在E语言中, <-是eventually运算符, 表示最终会执行, 但不一定是现在。而普通的car.moveTo(2,3)表示立刻执行得到结果。编译器负责识别所有的promise依赖, 并自动实现调度。

未完待续

【jQuery 中的编程范式(中)】相关文章

1. jQuery 中的编程范式(中)

2. jQuery 中的编程范式(上)

3. jQuery中的编程范式(下)

4. 精选在线课程:前端开发入门、进阶与实战(中文系列)

5. 2012年11月“我最喜爱的编程语言”排行榜出炉

6. 浅谈JavaScript编程语言的编码规范

7. 什么是最好的编程语言?

8. 你应该学习的最好的编程语言

9. 为什么说选择正确的编程语言很重要

10. 10个对开发项目有害的编程习惯

本文来源:https://www.51html5.com/a1114.html

点击展开全部

﹝jQuery 中的编程范式(中)﹞相关内容

「jQuery 中的编程范式(中)」相关专题

其它栏目

也许您还喜欢