MooTools团队成员:我们为何强于jQuery
jQuery非常容易学习,有很杰出的特性,但是当你从插件库里去找更多的功能时,发现官方的库里面根本就没有。插件特性非常好,但是也有很不好的地方……很快你就会被无数个可用的插件弄得晕头转向,你需要花很多时间去确定哪些插件才和核心库的质量相匹配。
因此,我必须首先很轻而易举地证明:jQuery是最受欢迎以及有更好表现的框架。他们是最好的。John Resig(jQuery的作者)在三天里曾七次说:“Microsoft演示了怎样把jQuery包含进了它的SDK,出席会议的人也非常肯定地谈论了这个框架。这些都清楚地让我看到:jQuery正在做一些正确的事情。”
我去参加这个会议的一部分任务就是尽可能多地参与jQuery的交流和学习(我曾经也很深入地学习过,但是我希望从开发团队和John那里学习到更多东西),同时也可以看一下我是否能够找到这个框架受欢迎的原因。
这里需要一些解释。MooTools团队从来没有真正关注过这个框架有多受欢迎。我们只是对写这个框架感兴趣,为我们自己使用和它本身目的去写,但是我们真的没有花很多精力去让其他人去使用这个框架。我们从不认为其它框架是我们的竞争对手——因为我曾经在很多场合都听说过这样的话,我们在和浏览器进行斗争,而不是相互斗争。在我的这篇文章中,我给你们的建议是:多尝试一些选 择,然后选择适合你的需求和你的风格的框架。你真的不能做一个坏的选择(因此,请停止争吵!)。jQuery、Prototype、YUI、Dojo、 MooTools——我们都只是用不同的方法在做同样的事。关于这一点写得有点多,因为我最近一直在思考这个。
Bill Scott在继续教我一些事情
当这个事情还在Boston的时候,我偶然遇见了Bill Scott。Bill是Yahoo的YUI团队的领导者,是一个很好的演讲者(尽管他在Boston的时候只做了一个关于他当前工作的五分钟的“闪电演 讲”)。一段时间以前,他帮助开始了Rico框架的开发,随后转到了YUI。接着在一年前,他离开了Yahoo,去了Netflix,带领团队做了许多工 作——不只是JavaScript(更多地关注了他们的新API和用户体验)。Netflix也在使用jQuery,因此我有机会和他坐下来谈论这些事 情。
在这里我要小心一些,并提醒正在阅读这篇文章的各位读者,我没有任何关于 jQuery的坏话要说,我也不想在这篇文章或者其他文章中挑起一场无谓的争论。jQuery和MooTools(以及Prototype、Dojo、 YUI等等等等)都是不同的而且没有相互竞争。它们用不同的方式解决问题,而我,就个人而言,碰巧喜欢Moot解决问题的方式以及它尝试解决问题的方式。 由于我还在学习jQuery,因此很容易我可能就会写一些不正确的jQuery语句,如果我有什么说得不正确的地方,还请大家谅解。
编程模式
在和Bill的谈话中,我说了我的一些关于“编程模式”的思考。我所谓的“编程模式”是这样的:当我写我的代码的时候,我可以选择做一个架构师,或者一个代码编写者。我可以设计,我也可以实施。事实上,我必须都做,但是我必须选择我更想做的一个,而且,在许 多情况下,不论是我还是其他人,都可能一个人去完成几乎所有的工作——尽管不是100%。
我究竟在说些什么?让我这样跟你讲:如果你写了20行代码,来描述一个页面上的用户交互,以便使页面看起来很 漂亮或者很易用,那么它是不是值得再多写5行或者10行代码来使其可以被重复使用?如果你只是依据经验直接编程,那么你需要写一遍又一遍。但是如果你按照 模式来写,你要写的代码会越来越少。
假设有这样一个用户体 验:当哟哦难怪乎点击“log in”按钮的时候,显示一个登陆框。当用户点击“log in”的时候,这个登陆框就出现。用户提交登陆表单,登陆框发出一个ajax请求并显示一个提示信息。当请求完成时,这个登陆框告诉用户已经登陆了并在过 一会儿后消失。
我可以用JavaScript来表现这些,就写在当前这个页面上或 者在我的全站代码里面(可能这个登陆框在每个页面上都有,是吧?)。它可能是这样一段代码(我会在某种程度上省略一些代码)——注意:我这里使用的是MooTools的语法,不过它看起来和现在的大多数框架都差不多,因为我们都相互借鉴好的点子:
window.addEvent('domready', function(){ $('loginLink').addEvent('click', function(e){ e.stop(); //don't follow the link $('loginPopup').show(); }); //loginPopup contains loginForm $('loginForm').addEvent('submit', function(e){ e.stop(); //don't submit the form $('loginForm').send({ onComplete: function(result) { $('loginPopup').set('html', result); //show the result (function(){ $('loginPopup').hide(); }.delay(1000)); //wait a sec, then hide the popup } }) }); });
美丽的直接了当,是吧?但是我们退后一步,然后问我们自己:这是什么模式呢?我们能再看到它的一部分吗?当然,一个弹出层包含一个form,然后提交、更新自己,然后做一些其它事情,而且可以再次出现。是吧?
这就是我所说的“编程模式”。在我以前的代码中,我 可能像上面的那样写代码。如果它是我的网站的一部分,我可能有一个名字空间(namespace)并且给它们一个叫做“showLogin”的方法,然后 在on domready事件中调用mySite.showLogin。或者,更可能是这样子的,我的网站需要很多这样的方法。showLogin、 logOut、makeToolTips、autoScroll、setupDraggers等等。然后我会写一个叫做mySite.init的方法来调 用所有的这些方法。
但是在回头看看我的老代码,我可能有一个巨大的domready方法,里面包括了所有的这些布局和交互的指令,一个很大的启动方法。
如果你从来没有写过这样的代码,你永远也不会知道维护这个代码的乐 趣。它会花费大量的精力去理解在第一件事情之后该是什么事情。再回头看看上面的示例代码,想像一下,如果我们遇到了类似的事情,但是有这个的3倍、5倍或 者10倍长,然后再想像一下一年以后我们再次碰到类似的事情。仅仅只是拆分这些行为就已经非常令人可怕了。
现在,让我们按照模式编程。一个弹出层,有一个form,并且自动更新。这就是我们要重复出现的模式。下面是一个MooTools类,实现了同样的东西:
var PopupForm = new Class({ Implements: [Events, Options], options: { requestOptions: {/*the user can fill in additional ajax options*/}, onComplete: $empty //do nothing on complete by default }, initialize: function(link, form, popup, options) { this.form = $(form); this.link = $(link); this.popup = $(popup); this.setOptions(options); this.makeRequest(); this.attach(); }, makeRequest: function(){ thisthis.request = this.form.retrieve('send', this.options.requestOptions); this.request.addEvent('complete', function(response){ popup.set('html', response); this.fireEvent('complete'); }.bind(this)); }, attach: function(){ this.link.addEvent('click', this.show.bind(this)); this.form.addEvent('submit', function(e){ e.stop(); this.request.send(); }.bind(this)); }, show: function(){ this.popup.show(); }, hide: function() { this.popup.hide(); } });
现在,不可否认的是我的类已经有两倍长了,但是它还仍然没有与我的登陆链接关联起来。要使其生效,我还需要对它进行初始化:
window.addEvent('domready', function(){ new PopupForm($('loginLink'), $('loginForm'), $('loginPopup'), { onComplete: function(){ (function(){ this.hide(); }.delay(1000, this)); //wait a sec, then hide the popup } }) });
有所取舍,但追求最大利益
除了代码变成了以前的两倍长以外,我还需要其他的9行代码去完成这个事情。15行代码和42行代码,看起来并不是个好的交易,但是我最近在我的所有代码里面都是这样写的。通过变换到这种思考方式,我从写很多很多代码段中解放出来,也节约了很多我以前根本没有考虑到的时间。
◆代码现在更加清晰易读了。我有一些很小的方法,它们只做一件事情,但是我知道它们在做什么以及为什么做。我的类的名字描述了它们要做的事情,而且它们也很小,也只做一件事情。如果我需要一个做两件事情的类,我会写两个类和一个小的控制类来调用它们。
◆代码可以重复使用——如果这个模式再度出现,我不需要再重复写这些代码。我曾经被它们出现的频率吓倒。我从来没有想过要重用一周以内的代码,但是我现在又开始重新使用他们了。
◆应用程序在哪——我现在正在做的web页面上,我的代码一般都很少。我不给页面单独写多少代码——我所做的只是针对每个给定的页面元素实例化一些类。这些小的“脚印”(页面执行时间?)意味这越少的代码对页面越好。
◆什么时候需要重构——可能我现在使用的框架已经有新的版本了,或者发现了一个新的浏览器bug,或者一个先的浏览器冲击了市场(例如Chrome),或者 我在自己的代码里面发现了一个bug(这是这几个原因里面最常见的),我需要立即修正它们。如果我为每个页面都写了有代码,那么我将需要一一修正。从另外 一个方面来说,如果我的页面只是实例化了我的几个类,我只需要修改我的类就行了。因为我控制着类的接口,所以我可以完全重写我的类,而不需要接触那些实例 化它们的代码。
◆最后,我彻底地改变了我的关于开发用户体验的思维方式。我更喜欢开发一个体验,然后重用它,而不是从头开始做一个新的。这可以创造一个连贯的体验,对我而言,意味着更好的用户体验。
可扩展性——因为我喜欢调整一些东西
这是这种编码方式给我代理的最后一个大好处,假设你现在写代码的方式能够利用它的可扩展性。 MooTools有一种基于类的层次结构(灵感来源于Dean Edwards的杰出工作),不要被这个名字给欺骗了。尽管它叫做类,实际上就只是一个object工厂,只不过让JavaScript里面的原型继承模 型变得更容易跟简单而已。
你当然不需要MooTools来做这些。JavaScript可以让你自己来实现。但是由于MooTools底层就是这样做的,因此很难避免让你去写这样的代码。写一个你自己的类真的很容易,扩展一个类也很容易——即使你没有写过这个类。
继续以我们上面提到的弹出表单为例。我们说我们需要一个弹出显示效果。为了让其变得有趣一点,我们假设已经有这样的代码了,因此我们不想去改变它,但是在这个页面中,我们想要这个弹出层有淡入淡出的效果,而不仅仅是出现。
MooTools允许你使用任何类(甚至你没有写过的类)并扩展成为一个新类。你不需要复制整个类以便添加你的不同代码——你只需要写那些你需要添加或者改变的部分就行了。
var PopupForm.Fx = new Class({ Extends: PopupForm, show: function(){ this.popup.setStyles({ opacity: 0, display: 'none' }).tween('opacity', 1); }, hide: function(){ this.popup.tween('opacity', 0).chain(function(){ this.popup.hide(); }.bind(this)); } });
现在,我 们有一个名字叫做“PopupForm.Fx”的新类了,这个类可以让弹出层淡入淡出。我们完全没有改变PopupForm类。另外,即使我们在后面发现 了PopupForm类的某个方法中有一个bug,我们可以在那个地方修正它,这将会修复这两个类以及所有我们使用了它的地方。
当我用这个方式写代码的时候,这意味着我在我工作的每一个页面、网站和项目 中都添加了一些新的工具到我的工具箱,当到下一个项目时我仍然可以使用。在过去的一两年里,我已经为MooTools发布了超过70个插件,而这还只是我 认为其他人能够用到的东西。表单验证、数据提取以及其他。我还为我自己的项目写了很多扩展,尽管它们不是很通用,但是在我的动作中并没有少重用它们。
揭开面纱看看
所有的这一切让我回到了我真正要说的:我是怎样看待MooTools以及它与其他框架有什么不一样。看看jQuery的表现(再次重复:jQuery仅仅只是不同——没有优劣之分),我认为MooTools和jQuery最大的不同在表现的解决方案上。
当和Bill Scott谈论他的团队和他们对jQuery的使用,我询问了他们对于按模式编程的所做的工作。Bill是相当适合问这个问题的人选,因为他去年花了很长 时间做了一个关于用户体验模式和YUI的演讲(我强烈推荐这个)。他领导了整个YUI项目模式库。他是一个一直致力于为重用而创建模式的人。我问了他关于 在jQuery中创建插件的机制,而不是扩展那些插件,坦率地说,这个插件机制对于我来说有一点尴尬(例如,你为了调用一个插件的方法,你必须这样 写:jQuery(domId).pluginName(”methodName”, [arg1, arg2]);——我希望我说的是对的——这对于我来说实在是神秘莫测)
在 听我谈完关于MooTools里面的可重用性和可扩展性模式之后,他做了一个很精妙的总结:他对JavaScript非常了解,可以利用 JavaScript本身内置的一些机制来完成他自己的一些方法。换句话说,他用jQuery进行Dom操作、实现特效以及用于一些其他插件,但是当他写 自己的代码的时候,他有了自己的类系统(尽管我不确定他用了“类”这个词——但是本质上有一些工厂去创建这些可重用和可扩展的object)。
JavaScript有它自己的重用方法
这个,坦率地讲,是我没有考虑到的。jQuery在模糊DOM方面做得非常非常的好,使得用户不再那么头疼。它使用起来非常简单而且非常善于表现。作为一个宽泛的框架(即在这个框架中,用户只是把JavaScript当作一种面向对象语言)的一部分来看,它是很明智的。
但是Bill随后说的话让我认真 地思考了在Ajax体验过程中的参与者。他是这样说的:“我从来没有认真地认为其他人不会这么做。”其他人可以使用jQuery(或者其他任何框架,甚至 只是“香草味”的JavaScript)而不利用JavaScript的任何本身特性(包括隐藏的特性)。
这里是三件真正触动我的事情。这三件真正的大事,对我而言,只会让我更加偏好MooTools。
每个人在某些事情上都是一只菜鸟
第一件大 事就是:使用框架的许多人(可能不是全部、或者绝大多数甚至只有三分之一——谁知道呢)都不这样认为。MooTools的用户,jQuery的用 户,YUI的用户,他们只是去写代码让页面去做他们想做的事情(要说清楚的是:直到今年年初,我用这种方式写了许多代码——但是现在我写的每个东西救护都 是一个类)。这些人为他们能在浏览器里做的那些事情感到激动,为他们的网站有多么有趣、强大、流畅、可用等等任何事情而激动。当他们需要重构的时候,这将 会非常的痛苦,不过他们还是很高兴——至少比没有东西好。
我认为现在可以安全地 说,在那些框架中,jQuery让这中体验变得非常容易切入。它是如此完整地模糊了浏览器,而在很多方面又是如此地像CSS,就像一剂入门毒药。你做了一 点点,然后你就再也不能停止。如果jQuery没有完成其他任何东西,只是把JavaScript介绍给那些人然后让他们坚持使用,这就是个不小的成就。
但是我们最终还是要学习
第二件大事 对我来说变得很明显,无论迟早,人们越来越熟练地用这种方式写代码,当他们写的时候,他们会想变得更有效率。他们可能不会选择跟我现在一样的道路——那些 框架的数目已经说明那里有很多方式去给这只猫上色。但是他们将会度过这样一个阶段:“嗨,你看,我能做一个流畅的Ajax登陆框了!”然后他们会想着去开 始开发一些更加容易重用、容易维护和容易扩展的代码。在那个时候,他们将会重新阅读“Crockford的书”,然后反问自己为什么他们不在那里做一些工作呢?
再说一遍,不管你在用什么框架,这都会发生。第1步:学习JavaScript基本语法。第2步:学习利用 框架做一些有趣的效果或者ajax应用。第4步(译者注:貌似是第3步,不过不必较真)到719步:犯一大堆很愚蠢的错误并学习一些东西。第720步:真正掌握JavaScript然后很蔑视地回头看看你以前的工作。
在框架中,要紧的是它里面的东西
这个,最终,把我带向了第三件大事。这个事件让我确定了我对MooTools的选择。所有的框架都变得日益相似。即有一天他们最终会看起来像这些东西:
fetch(element).verb(details).verb(details).verb(details)
唯一的真正区别只是他们的术语不一样。一个框架可能使用$,另外一个可能做同样的事情,例如Y.get或者jQuery(id),未来的框架只是使用了同意 的不用词汇而已。在边缘,他们都做同样的事情。这是因为我们都彼此看到了对方工作中好的模式然后加以吸收——再重申一遍,我们全部在和同一个东西做斗争 ——浏览器——为了同样的目的。
但是在核心深处,他们是各不相同的。这些框架的开发者不会花大量的时间来写一个让登陆框变化的代码。当然,他们为各自的项目完成这样的工作,这些框架本身的工作在内部进行。 迟早,任何人开始学习其中的一个框架,就会接触到边缘代码,然后就会想去利用核心的代码。他们会重新阅读“Crockford的书”并想:“我在用很困难 的方式做这个——我不停地重复太多了,那个东西和我昨天写的东西几乎一样,虽然不完全一样。那里肯定有更好的方式。”像Bill Scott这样的人已经在做这些了,但是每个刚刚开始的人还深深地铭记着从屏幕上看到的那些绚丽的东西(当然,他们应该这样)。
当他们真的够好并且已经准备好去实施下一步的时候,他们将会看看他们库的内核来进行选择。无论开始使用什么框架,在几个月 之后,他们写出了代码,他们会期待那些框架专家的答案,专家们正在写内核。每个框架都有开发插件和对其进行扩展的机制,我承认我只是在一定程度上熟悉其中 的一部分。我不能对他们给出任何评价除了MooTools,因此我将对那些东西约束我的言论。几乎所有的MooTools的功能性的东西都是通过扩展本地对象(数组、字符串等)或者类实现的。贯穿整个库的主要(浅)继承和重用都通过易读和易用的方式得到了证明。正是因为如此,我自己的代码也比以前要好得多了。
为什么MooTools有震撼力
现在,当人们问我为什么我觉得MooTools是更好的选择,我仍然会告诉他们:不是什么这个框架好于其它框架,所有的框架都是好的选择,对于我个人而言, 选择MooTools的原因仅仅只是因为我能最终用语言表现出来一些东西。所有的框架都用不同的方式达到了类似的目的,但是,至少对于我而言,MooTools再一次帮助我解决了我的设计和实现挑战,这就是我足够明确的理由。