2009年8月16日星期日

jQuery is DSL (Part 2 - jQuery)

jQuery的Internal DSL形式

在上一篇文章里面,我们了解到了Internal DSL的具体形式,形如:

/* Method Chaining */
computer()
  .processor()
    .cores(2)
    .i386()
  .disk()
    .size(150)
  .disk()
    .size(75)
    .speed(7200)
    .sata()
  .end();


然后我们在看看一段典型的jQuery代码:

$("ul#contacts li.item")
  .find("span.name")
    .click(function(e) { $(e.target).siblings(".more").toggle(); })
    .end()
  .find("input.delete")
    .click(function(e) { $(e.target).parents(".item").remove(); })
    .end()
  .find("div.more")
    .hide()
    .end();


从结构上来说,是不是跟上面那一段Internal DSL的例子很相似?就算我们不看对应的HTML,我们也能猜到这段jQuery代码的含义:
  • 遍历<ul id="contacts">中的每一个<li class="item">
    (这看起来是个联系人列表)
    • 对于里面的<span class="name">
      • 绑定click事件,操作是显示/隐藏class="more"兄弟节点
        (这是估计联系人姓名,点击后切换详细信息的显示/隐藏)
    • 对于里面的<input class="delete">
      • 绑定click事件,操作是把class="item"父节点删除
        (这应该是用来删除联系人的)
    • 对于里面的<div class="more">
      • 隐藏这个div
        (默认隐藏详细信息?)
从这里我们已经能够看出jQuery的Internal DSL形式带来的好处——编写代码时,让代码更贴近作者的思维模式;阅读代码时,让读者更容易理解代码的含义。不信?我们看看与jQuery拥有相似功能的Prototype是如何实现上述逻辑:

$$("ul#contacts li.item span.name")
  .invoke("observe", "click",
    function(e) { $(e.target).next(".more").toggle(); });
$$("ul#contacts li.item input.delete")
  .invoke("observe", "click",
    function(e) { $(e.target).up(".item").remove(); });
$$("ul#contacts li.item div.more")
  .invoke("hide");


这是我用Prototype所能写出的最贴近Internal DSL的形式了。(如果你能够写出一个更自然的版本,欢迎分享。)在Prototype里面,能够返回一组元素的操作就只有$$(),并且它只能作用于全局,缺乏jQuery中find()或者filter()的功能,所以这一组描述联系人列表行为的语句无法组合在一起,必须逐一定义每类元素的行为。此外,此例子中每类元素都仅仅指定了一个行为,因此Prototype的invoke()写法看起来还是和jQuery的click()写法很相近的。但如果一类元素拥有多个行为,Prototype的invoke()就不能好像jQuery那样链式调用下去了,必须每一个行为重头写一个$$(),或者把invoke()改成each()加匿名函数。无论是那种做法,都只会降低代码的可读性。

jQuery的语法分析器

我们都知道,Internal DSL的实现依赖于对语法分析器的封装,对Internal DSL的调用其实都是对语法分析器的调用,经过语法分析后再构造出对底层API的调用。例如jQuery当中的click(),它依赖于当前的状态,也就是前面$()筛选出来的节点集合,把click()解释为要为这一组节点绑定DOM的click事件,最后再调用DOM API完成任务。在这个例子当中,DOM API相对jQuery API而言就是底层API了。

jQuery可以说是挑了一个最容易实现的语法模型来做,永远只有一种token,因此永远也只有一种状态,这种状态当然也是永远有效的,你根本不可能给jQuery输入一个当前状态无效的token。jQuery的唯一状态就是一个jQuery对象实例,其本质就是一个元素集合。读入的token可能是各种针对这个元素集合的操作,但它的返回一定还是一个元素集合。这使得jQuery的语法分析器不会进入无效状态,也就无需判断无效状态,因此大大简化了Internal DSL实现中常见的一个难题。

小结

通过拿jQuery和Prototype做对比,我们可以发现jQuery用非常低的成本实现了Internal DSL,同时带来了Prototype所没有的明显好处。这可以看作是一个很好的范例——如果你需要描述的业务逻辑能够归纳为简单的语言模式,为此实现一门Internal DSL的性价比将会是很高的。你需要做的仅仅是为这个简单的语言模型实现一个简单的解释器,接着你就可以享受贴近人类思维模式的接口了。

最后,如果你喜欢我的文章,可以考虑订阅我的博客:

没有评论:

发表评论