从jQuery谈库与框架的设计之优劣

jopen 10年前

jQuery是业内知名的javascript框架,它的实现和设计可以说代表了javascript界最高的水平,本文试从四个方面来以jQuery为例总结库与框架设计的原则和优劣判断。

解决问题

首先请看一个我实现的框架,我把这个库称为四则运算。

  1. function add(a,b) {  
  2.     return a+b;  
  3. }  
  4. function mul(a,b) {  
  5.     return a*b;  
  6. }  
  7. function minus(a,b) {  
  8.     return a-b;  
  9. }  
  10. function div(a,b) {  
  11.     return a/b;  

这个库的API简洁优美,实现的更是优雅无比,它把四则运算统一成了函数形式,使得我们的开发更加方便。最强大的是,这个做法使得四则运算支持函数式编程,比如:

  1. function acc(a,b,f) {  
  2.     var jieguo = a; //http://weibo.com/2178807082/zk1kOcMPU  
  3.     for(var i = 1; i<b; i++) {  
  4.         jieguo = f(jieguo,a);  
  5.     }  
  6.     return jieguo  
  7.  
  8. }  
  9.  

这样,通过acc函数,我们可以轻易实现n次方运算,这正是函数式编程之美。

举这个例子是为了告诉大家,一个框架/库其实可以不需要解决任何问题——只要你会乱用概念、自吹和哄骗新手就够了。

下面我们来看看一个非常成功的框架——jQuery解决的问题,让我们来看看jQuery首页的"Brief Look"中给出的第一个例子。

  1. DOM Traversal and Manipulation  
  2. $( "button.continue" ).html( "Next Step..." ) 

嗯,没错,jQuery希望帮助我们解决遍历DOM的问题,如果没有jQuery,我们大概要写一个traversal函数了。

  1. function traversal(node, f) {  
  2.     f(node);  
  3.     if(node.children.length) {  
  4.         for(var i = 0; i<node.children.length;i++)  
  5.             traversal(node.children[i],f);  
  6.     }  
  7. }  
  8.  
  9. traversal(document.body,function(element){  
  10.     if(element.tagName=="button" && element.className.match(/continue/)) {  
  11.         element.innerHTML = "Next Step...";  
  12.     }  
  13. }) 

traversal这个函数真的是超麻烦不是么?竟然有175个字符呢!使用起来也有166个字符,用了jQuery之后,只要45个字符就可以搞定呢!好神奇!

好吧原谅我刚才把思维模式切换到了"write less, do more"模式,jQuery正是通过一个字符一个字符地节约程序员的工作量来达到这一伟大目标的。

压缩后仍达97k的jQuery竟然帮助我们少写了这么多代码,好神奇啊啊啊!这正是库/框架设计的要点,那就是"没有问题创造问题也要解决问题!"

命名

命名问题对于库/框架来说,尤其重要。

总的来说,如同正统程序员那样追求命名的易读、易懂、与原生一致的话,你的库/框架不会有任何出彩的地方。

好的库/框架,命名有几个原则,第一个原则,就是要有厚重的历史感。

在80年代,因为C语言支持的变量名最长为8字节,聪明的程序员们使用了一种缩写方式,保留发音的轻辅音字母,省略元音字母和部分浊辅音字母。

比如:

  • button=>btn
  • text=>txt
  • search=>srch
  • fuck=>fk
  • click=>clk
  • double=>db

没错,这样虽然省略了字母,但是英文好的人仍然可以读出单词来,而现在,虽然我们完全没有这种需要,我们也可以为了给新人以距离感和绝对的震慑而使用这种命名。

还有一种历史上的命名方式:匈牙利命名。

匈牙利命名使用简写的类型作为变量的前缀,比如

  • iCount表示int类型的count
  • szText表示字符串类型的text
  • bIsNumber表示布尔类型isNumber
  • dRate表示表示双精度浮点类型rate

是不是看上去很酷?值得一提的是,sz表示以0结尾的字符串,这是C语言中字符串的实现方式,JS中完全不是这样实现的字符串,所以这样用可以大大提升你在新手中的地位,他们做梦也猜不到sz是字符串的意思。

另外,其实js是弱类型语言,变量根本就跟类型不是绑定的。

除了历史之外,特殊符号也是我们的首选,在javascript中,$美元符号和_下划线是非常棒的选择。这当中的代表作当属jQuery和underscore.

jQuery的$使用也并非原创,先行者是prototype.js,这也让jQuery的做法具有相当厚重的历史感。同时,美元符号比下划线强的地方 是,美元符号还具有吉祥如意的寓意,咱们写代码的,出来不就是混口饭吃,如果代码经常出现美元,一定会给我们带来很多财运的。

占领这些特殊符号可以让别的框架无符号可用,当年jQuery和prototype.js争霸之时,jQuery就被迫搞出来了 noconflict——尽管基本没见过有人用过,毕竟要把满篇的$改成jQuery还是颇费体力的,多数人会选择放弃一个库。(什么你说根本不需要替 换?直接在外面套个闭包就能解决问题?醒醒吧亲,能同时用俩带$库的工程师怎么可能会那种高级玩意儿啊,人家都是实践派好吧?)

不过大家不要伤心,虽然没有了$和_可以抢,ES5为我们带来了更多奇形怪状的、键盘轻易都输入不来的标示符可用字符。我首先要推荐的是两个零宽字 符<zwnj>和<zwj>,这俩字符一个是连接符,一个是非连接符,它们的厉害之处在于,不可见,通过这俩字符,你可以制造出 假的$来。

请看以下代码:

var $ = 1; 

估计你做梦也想不到这变量其实是$\u200D(<zwj>)吧......

通过\u200D和\u200C的组合,可以制造神奇的代码出来,你的用户一定会交口称赞你的魔法代码的!

不止如此!更多奇怪的字符等你挖掘!

接口设计

除了命名,接口设计也是框架的核心之一。

那些平庸的框架会用"单一职责"原则来设计接口:不论是类还是函数,一个只做一件事,而且跟命名 中所说的完全一致。

Noooooooooooo ! 这也太无趣了!

我们来看看jQuery的$有多少种用法!摘自官方文档:

  • jQuery( selector [, context ] )
  • jQuery( element )
  • jQuery( elementArray )
  • jQuery( object )
  • jQuery( jQuery object )
  • jQuery()
  • jQuery( html [, ownerDocument ] )
  • jQuery( html, attributes )
  • jQuery( callback )

没错!这个函数(jQuery就是)居然有9种重载!而且重载中最少包含了3种毫不相干的使用方法!一段时间里,我曾经在面试中问所有声称自己熟悉jQuery的面试者这个函数有多少种用法,可以答上三种以上的仅有1人,而没有人答出来过超过5种!

可以说,$在jQuery使用者的眼里就是一个神!你能想到的事情它都能做!在可以预见的将来,相信jQuery会结合人工智能,做到不论你想实现任何功能,都只需要写同样的代码:

  1. $(); 

怎么样,看清楚接口设计的原则了么?那就是:尽量把功能加到以前的接口上,通过加参数、区分参数类型来添加功能,不论它们有没有联系,也不论API的名称是什么!哦...... 对了这个应该结合上文提到的命名,请使用没有任何意义的魔法变量名!

耦合

你可能常常听到一些旧时代的程序员讲,程序必须"高内聚、低耦合"。然而,这个说法具有极大的误导性。

我们先来看看jQuery的事件绑定:

  1. var hiddenBox = $( "#banner-message" );  
  2. $( "#button-container button" ).on( "click"function( event ) {  
  3.   hiddenBox.show();  
  4. });  
  5.  
  6. 假如我不想使用选择器,只想要绑定事件到一个DOM元素上怎么办呢?答案是,你要首先把他变成一个jQuery对象才行  
  7.  
  8. $(document.querySelector("#button-container button")).on( "click"function( event ) {  
  9.   hiddenBox.show();  
  10. }); 

接下来我们来看jQuery的ajax部分:

  1. $.ajax({  
  2.   url: "/api/getWeather",  
  3.   data: {  
  4.     zipcode: 97201  
  5.   },  
  6.   success: function( data ) {  
  7.     $( "#weather-temp" ).html( "<strong>" + data + "</strong> degrees" );  
  8.   }  
  9. }); 

假如你仅仅想要使用$.ajax这个功能,不想使用选择器等功能怎么办呢?答案很简单:像你需要使用全部功能一样,在页面引用仅有97k的jQuery,然后使用$.ajax。

从这个例子中我们可以看出,耦合对于一个库的重要性:耦合让你那些不太被人接受的功能,跟着受欢迎的功能一起被强制使用,这样,用户就会逐渐被强奸,逐渐变得认为理所当然,这正是jQuery能够成为"事实标准"的奥秘。