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代码的含义:
- 遍历
-
中的每一个
(这看起来是个联系人列表)- 对于里面的
- 绑定
click
事件,操作是显示/隐藏class="more"
兄弟节点
(这是估计联系人姓名,点击后切换详细信息的显示/隐藏)
- 绑定
- 对于里面的
- 绑定
click
事件,操作是把class="item"
父节点删除
(这应该是用来删除联系人的)
- 绑定
- 对于里面的
- 隐藏这个
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的性价比将会是很高的。你需要做的仅仅是为这个简单的语言模型实现一个简单的解释器,接着你就可以享受贴近人类思维模式的接口了。