jQuery is DSL

jQuery剛剛出來的時候,我沒有太多關注它,覺得這不過是Yet Another JavaScript Library。早期的jQuery專注于DOM節點的篩選與操作,不提供眾多的基礎類擴展,更不提供UI組件,因此體積能夠做到很小。然而,我實在看不出它和我熟悉的Prototype比有什么明顯的優勢——jQuery能做的各項獨立的操作,Prototype都能做。

后來用jQuery的人越來越多,并且大家都愛用它的鏈式方法調用,甚至還把這種寫法推廣到其它語言中去。例如ASP.NET MVP 就把他的服務器端C#組件設計為支持鏈式方法調用的。這時候我才開始關注jQuery,并且逐漸喜歡上了鏈式方法調用的寫法,也在我自己的JavaScript組件中實現類似的API(參考和)。最后,我突然明白到,這其實就是一種Internal DSL嘛!

在這篇文章里,我準備先討論Internal DSL,在下一篇文章里面再解釋為什么jQuery是Internal DSL。現在我們就從最根本的問題開始吧——

什么是Internal DSL?

DSL是指Domain Specific Language,也就是用于描述和解決特定領域問題的語言。例如說,我們有專門描述字符串特征的正則表達式,有專門描述數據庫查詢的SQL,有專門描述XML結構的DTD和XSD,甚至有專門描述XML變換的XSLT,這些都是DSL。

當然,并非我們關注的領域都有現成的DSL,這時候我們有三個選擇:

使用通用語言描述該領域的問題(non-DSL)

發明一門全新的語言描述該領域的問題()

在一門現成語言內實現針對領域問題的描述()

例如說,我們現在要描述一個很簡單的金融領域問題,“我在花旗銀行存款$200”這樣一句話對應的三種法寫法可能是:(假設已經存在I和CitiBank兩個實體實例)

I.DepositTo(new USD(200), CitiBank); /* C# */

I deposit 200USD to CitiBank /* E-DSL */

I.deposit(200.USD()).to(CitiBank); /* I-DSL */

第1種做法的成本最低,你只需要有OO的思想就可以了,你總能把實體類設計出來,但可能和人類描述此領域問題的思維方式有一定偏差(為什么USD可以new?為什么不是deposit [something] to [somewhere]?)。

 

第2種做法的成本最高,你需要寫一個全新的解釋器,至少是寫一組全新的規則,然后讓YACC這類工具幫你生成一個解釋器,但這樣出來的語法最貼近人類思維方式,甚至就如同自然語言一樣流暢。

第3種做法術語上述兩者的折中方案,如果語法不太復雜可以使用Builder模式實現語法分析,寫出來的語法相當貼近自然語言,但還是有學習門檻。由于腳本語言有相當的靈活性,所以現在很多人傾向于選擇在腳本語言內實現Internal DSL。

如何構造Internal DSL?

常見的兩種Internal DSL實現方法是和。如果我們需要描述一臺機器的硬件組成,兩種實現方式的代碼分別如下:

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

/* Function Sequence */
computer();
  processor();
    cores(2);
    processorType(i386);
  disk();
    diskSize(150);
  disk();
    diskSize(75);
    diskSpeed(7200);
    diskInterface(SATA);

無論是哪一種寫法,中間都必須寫一個分析器層。就如同語法分析器需要使用狀態機一樣,Internal DSL的實現也必須內置一個狀態機,以記錄當前執行到什么狀態了,并且接下來可以轉移到哪些有效狀態。

由于這不是一篇專門講語法分析器和狀態機實現的文章,所以我們把關注點保持在API層面就可以了,不深入討論其實現細節和成本。我們知道鏈式方法調用能夠實現Internal DSL就夠了,至于jQuery是如何利用好這一點的,我們在下一篇文章里再作討論。

小結

在這篇文章里,我們了解了Internal DSL與External DSL之間的區別,同時還了解到實現Internal DSL的具體方式,這為我們接下來討論jQuery的Internal DSL式接口做好了鋪墊。在下一篇文章里,我們將深入地來看看為什么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代碼的含義:

遍歷

  • 中的每一個


  • (這看起來是個聯系人列表)


隱藏這個div
(默認隱藏詳細信息?)

綁定click事件,操作是把class="item"父節點刪除
(這應該是用來刪除聯系人的)

綁定click事件,操作是顯示/隱藏class="more"兄弟節點
(這是估計聯系人姓名,點擊后切換詳細信息的顯示/隱藏)

對于里面的

對于里面的

對于里面的

從這里我們已經能夠看出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的性價比將會是很高的。你需要做的僅僅是為這個簡單的語言模型實現一個簡單的解釋器,接著你就可以享受貼近人類思維模式的接口了。


來源:Cat Chen - 博客園

上一篇: 巧妙使用checkbox制作純css動態導航欄程序媛_Mickey

下一篇: JavaScript中的陷阱大集合

分享到: 更多
双色球历史结果1000期 北京赛车滚雪球计划软件 北京pk拾人工免费计划 mg摆脱豪华版讲解 重庆时时龙虎和走势 2019够力七星彩安装 大亨pk10专业版计划 重庆时时彩龙虎正规吗 打印软件免费版 三公怎么玩才能赢钱 北京pk拾七码计划 哪里有快三计划软件卖 金殿国际棋牌 北京pk10全天精准计划 龙虎游戏官网下载 抢庄牛牛可以开挂吗