jQuery 中的编程范式(上)

浏览器前端编程的面貌自2005年以来已经发生了深刻的变化,这并不简单的意味着出现了大量功能丰富的基础库,使得我们可以更加方便的编写业务代码,更重要的是我们看待前端技术的观念发生了重大转变,明确意识到了如何以前端特有的方式释放程序员的生产力。本文将结合jQuery源码的实现原理,对javascript中涌现出的编程范式和常用技巧作一简单介绍。

       1. AJAX: 状态驻留,异步更新

       首先来看一点历史。

       A. 1995年Netscape公司的Brendan Eich开发了javacript语言,这是一种动态(dynamic)、弱类型(weakly typed)、基于原型(prototype-based)的脚本语言。

       B. 1999年微软IE5发布,其中包含了XMLHTTP ActiveX控件。

       C. 2001年微软IE6发布,部分支持DOM level 1和CSS 2标准。

       D. 2002年Douglas Crockford发明JSON格式。

       至此,可以说Web2.0所依赖的技术元素已经基本成形,但是并没有立刻在整个业界产生重大的影响。尽管一些“页面异步局部刷新”的技巧在程序员中间秘密的流传,甚至催生了bindows这样庞大臃肿的类库,但总的来说,前端被看作是贫瘠而又肮脏的沼泽地,只有后台技术才是王道。到底还缺少些什么呢?

       当我们站在今天的角度去回顾2005年之前的js代码,包括那些当时的牛人所写的代码,可以明显的感受到它们在程序控制力上的孱弱。并不是说2005年之前的js技术本身存在问题,只是它们在概念层面上是一盘散沙,缺乏统一的观念,或者说缺少自己独特的风格, 自己的灵魂。当时大多数的人,大多数的技术都试图在模拟传统的面向对象语言,利用传统的面向对象技术,去实现传统的GUI模型的仿制品。

       Ajax这一前端特有的概念迅速将众多分散的实践统一在同一口号之下,引发了Web编程范式的转换。所谓名不正则言不顺,这下无名群众可找到组织了。在未有Ajax之前,人们早已认识到了B/S架构的本质特征在于浏览器和服务器的状态空间是分离的,但是一般的解决方案都是隐藏这一区分,将前台状态同步到后台,由后台统一进行逻辑处理,例如ASP.NET。因为缺乏成熟的设计模式支持前台状态驻留,在换页的时候,已经装载的js对象将被迫被丢弃,这样谁还能指望它去完成什么复杂的工作吗?

       Ajax明确提出界面是局部刷新的,前台驻留了状态,这就促成了一种需要:需要js对象在前台存在更长的时间。这也就意味着需要将这些对象和功能有效的管理起来,意味着更复杂的代码组织技术,意味着对模块化,对公共代码基的渴求。

       jQuery现有的代码中真正与Ajax相关(使用XMLHTTP控件异步访问后台返回数据)的部分其实很少,但是如果没有Ajax, jQuery作为公共代码基也就缺乏存在的理由。

       2. 模块化:管理名字空间

       当大量的代码产生出来以后,我们所需要的最基础的概念就是模块化,也就是对工作进行分解和复用。工作得以分解的关键在于各人独立工作的成果可以集成在一起。这意味着各个模块必须基于一致的底层概念,可以实现交互,也就是说应该基于一套公共代码基,屏蔽底层浏览器的不一致性,并实现统一的抽象层,例如统一的事件管理机制等。比统一代码基更重要的是,各个模块之间必须没有名字冲突。否则,即使两个模块之间没有任何交互,也无法共同工作。

       jQuery目前鼓吹的主要卖点之一就是对名字空间的良好控制。这甚至比提供更多更完善的功能点都重要的多。良好的模块化允许我们复用任何来源的代码,所有人的工作得以积累叠加。而功能实现仅仅是一时的工作量的问题。jQuery使用module pattern的一个变种来减少对全局名字空间的影响,仅仅在window对象上增加了一个jQuery对象(也就是$函数)。

       所谓的module pattern代码如下,它的关键是利用匿名函数限制临时变量的作用域。

  1. var feature =(function() {
复制代码

       js本身缺乏包结构,不过经过多年的尝试之后业内已经逐渐统一了对包加载的认识,形成了RequireJs库这样得到一定共识的解决方案。jQuery可以与RequireJS库良好的集成在一起, 实现更完善的模块依赖管理。http://requirejs.org/docs/jquery.html

  1. require(["jquery", "jquery.my"], function() {
  2.     //当jquery.js和jquery.my.js都成功装载之后执行
  3.     $(function(){
  4.       $(‘#my’).myFunc();
  5.     });
  6.   });
复制代码


       通过以下函数调用来定义模块my/shirt, 它依赖于my/cart和my/inventory模块,

  1. require.def(“my/shirt”,
复制代码

       3. 神奇的$:对象提升

       当你第一眼看到$函数的时候,你想到了什么?传统的编程理论总是告诉我们函数命名应该准确,应该清晰无误的表达作者的意图,甚至声称长名字要优于短名字,因为减少了出现歧义的可能性。但是,$是什么?乱码?它所传递的信息实在是太隐晦,太暧昧了。$是由prototype.js库发明的,它真的是一个神奇的函数,因为它可以将一个原始的DOM节点提升(enhance)为一个具有复杂行为的对象。在prototype.js最初的实现中,$函数的定义为

  1. var $ = function (id) {
  2.   return ”string” == typeof id ? document.getElementById(id) : id;
  3. };
复制代码


       这基本对应于如下公式

  1. e = $(id)
复制代码


       这绝不仅仅是提供了一个聪明的函数名称缩写,更重要的是在概念层面上建立了文本id与DOM element之间的一一对应。在未有$之前,id与对应的element之间的距离十分遥远,一般要将element缓存到变量中,例如

  1. var ea = docuement.getElementById(‘a’);
  2. var eb = docuement.getElementById(‘b’);
  3. ea.style….
复制代码


       但是使用$之后,却随处可见如下的写法

  1. $(‘header_’+id).style…
  2. $(‘body_’+id)….
复制代码


       id与element之间的距离似乎被消除了,可以非常紧密的交织在一起。

       prototype.js后来扩展了$的含义,

  1. function $() {
  2.   var elements = new Array();

  3.   for (var i = 0; i < arguments.length; i++) {
  4.       var element = arguments[i];
  5.       if (typeof element == ’string’)
  6.         element = document.getElementById(element);

  7.       if (arguments.length == 1)
  8.         return element;

  9.       elements.push(element);
  10.   }

  11.   return elements;
  12. }
复制代码


       这对应于公式

  1. [e,e] = $(id,id)
复制代码


       很遗憾,这一步prototype.js走偏了,这一做法很少有实用的价值。

       真正将$发扬光大的是jQuery, 它的$对应于公式

  1. [o] = $(selector)
复制代码


       这里有三个增强

       A. selector不再是单一的节点定位符,而是复杂的集合选择符

       B. 返回的元素不是原始的DOM节点,而是经过jQuery进一步增强的具有丰富行为的对象,可以启动复杂的函数调用链。

       C. $返回的包装对象被造型为数组形式,将集合操作自然的整合到调用链中。

       当然,以上仅仅是对神奇的$的一个过分简化的描述,它的实际功能要复杂得多. 特别是有一个非常常用的直接构造功能

  1. $(“<table><tbody><tr><td>…</td></tr></tbody></table>”)….
复制代码


       jQuery将根据传入的html文本直接构造出一系列的DOM节点,并将其包装为jQuery对象. 这在某种程度上可以看作是对selector的扩展: html内容描述本身就是一种唯一指定。

       $(function{})这一功能就实在是让人有些无语了, 它表示当document.ready的时候调用此回调函数。真的,$是一个神奇的函数, 有任何问题,请$一下。

       总结起来, $是从普通的DOM和文本描述世界到具有丰富对象行为的jQuery世界的跃迁通道。跨过了这道门,就来到了理想国。

       4. 无定形的参数:专注表达而不是约束

       弱类型语言既然头上顶着个”弱”字, 总难免让人有些先天不足的感觉. 在程序中缺乏类型约束, 是否真的是一种重大的缺憾? 在传统的强类型语言中, 函数参数的类型,个数等都是由编译器负责检查的约束条件, 但这些约束仍然是远远不够的。一般应用程序中为了加强约束, 总会增加大量防御性代码, 例如在C++中我们常用ASSERT, 而在java中也经常需要判断参数值的范围:

  1. if (index < 0 || index >= size)
  2.     throw new IndexOutOfBoundsException(
  3.         ”Index: ”+index+”, Size: ”+size);
复制代码


       很显然, 这些代码将导致程序中存在大量无功能的执行路径, 即我们做了大量判断, 代码执行到某个点, 系统抛出异常, 大喊此路不通. 如果我们换一个思路, 既然已经做了某种判断,能否利用这些判断的结果来做些什么呢? javascript是一种弱类型的语言,它是无法自动约束参数类型的, 那如果顺势而行,进一步弱化参数的形态, 将”弱”推进到一种极致, 在弱无可弱的时候, weak会不会成为标志性的特点?

       看一下jQuery中的事件绑定函数bind,

       A. 一次绑定一个事件 $(“#my”).bind(“mouseover”, function(){});

       B. 一次绑定多个事件 $(“#my”).bind(“mouseover mouseout”,function(){})

       C. 换一个形式, 同样绑定多个事件 $(“#my”).bind({mouseover:function(){}, mouseout:function(){});

       D. 想给事件监听器传点参数 $(‘#my’).bind(‘click’, {foo: “xxxx”}, function(event) { event.data.foo..})

       E. 想给事件监听器分个组 $(“#my”).bind(“click.myGroup″, function(){});

       F. 这个函数为什么还没有疯掉???


       就算是类型不确定, 在固定位置上的参数的意义总要是确定的吧? 退一万步来说, 就算是参数位置不重要了,函数本身的意义应该是确定的吧? 但这是什么

  1. //取值
  2. value = o.val(),
  3. //设置值
  4. o.val(3)
复制代码

       一个函数怎么可以这样过分, 怎么能根据传入参数的类型和个数不同而行为不同呢? 看不顺眼是不是? 可这就是俺们的价值观. 既然不能防止, 那就故意允许. 虽然形式多变, 却无一句废话. 缺少约束, 不妨碍表达(我不是出来吓人的).

       未完待续。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章