全棧式JavaScript
如今,在創(chuàng)建一個(gè)Web應(yīng)用的過(guò)程中,你需要做出許多架構(gòu)方面的決策。當(dāng)然,你會(huì)希望做的每一個(gè)決定都是正確的:你想要使用能夠快速開(kāi)發(fā)的技術(shù),支持持續(xù)的迭代,最高的工作效率,迅速,健壯性強(qiáng)。你想要精益求精并且足夠敏捷。你希望你選擇的技術(shù)能夠在短期和長(zhǎng)期上都讓你的項(xiàng)目取得成功。但這些技術(shù)都不是輕而易舉就能選出來(lái)的。
我的經(jīng)驗(yàn)告訴我,全棧式JavaScript符合了這所有的要求??赡苣阋呀?jīng)發(fā)現(xiàn)了些許端倪,又或許你已經(jīng)在考慮它的實(shí)用性,并且在和朋友討論爭(zhēng)論它的話題。但是你是否親自嘗試過(guò)呢?在這篇文章中,我會(huì)對(duì)于全棧式JavaScript給出一個(gè)比較全面的介紹,為什么它會(huì)是正確的選擇,它又是如何施展它的魔法的。
先給出一個(gè)概括預(yù)覽:

接下來(lái)我會(huì)一項(xiàng)一項(xiàng)地介紹這些組件。但是在這之前,我們簡(jiǎn)短地回顧一下,我們是如何發(fā)展到現(xiàn)在的這個(gè)階段的。
我為什么選擇用JavaScript
從1998年開(kāi)始,我就是一個(gè)Web開(kāi)發(fā)者。當(dāng)時(shí),我們使用Perl進(jìn) 行大多數(shù)的服務(wù)器端的開(kāi)發(fā);但是從那時(shí)候開(kāi)始,我們就在客戶端使用JavaScript。Web服務(wù)器端的技術(shù)已經(jīng)發(fā)生了翻天覆地的變化:我們被一波又一 波的技術(shù)潮流推著往前走,PHP,ASP,JSP,.NET,Ruby,Python,這里只列出了幾個(gè)例子。開(kāi)發(fā)人員們開(kāi)始意識(shí)到,在服務(wù)器端和客戶端 使用不同的語(yǔ)言使得事情變得復(fù)雜化。
在早期的PHP和ASP的時(shí)代,那個(gè)時(shí)候模板引擎還僅僅是個(gè)設(shè)想,開(kāi)發(fā)人員們?cè)贖TML中嵌入他們的應(yīng)用代碼。我們經(jīng)常可以看到下面這種腳本嵌入的寫法:
- <script>
 - <?php
 - if ($login == true){
 - ?>
 - alert("Welcome");
 - <?php
 - }
 - ?>
 - </script>
 
或者更糟糕:
- <script>
 - var users_deleted = [];
 - <?php
 - $arr_ids = array(1,2,3,4);
 - foreach($arr_ids as $value){
 - ?>
 - users_deleted.push("<php>");
 - <?php
 - }
 - ?>
 - </script>
 
對(duì)于新手來(lái)說(shuō),很容易被不同語(yǔ)言之間的用法而混淆,犯下一些很典型的錯(cuò)誤,比如for和foreach。更為不爽的是,以這樣的方式來(lái)寫代碼,使得服務(wù)器 端和客戶端很難以非常和諧的方式處理相同的數(shù)據(jù)結(jié)構(gòu),即使是今天也是如此(當(dāng)然除非你的開(kāi)發(fā)團(tuán)隊(duì)有專職的前端和后端工程師 — 但即使他們之間能夠共享信息,但仍然不能僅僅基于對(duì)方的代碼進(jìn)行合作)。
- <?php
 - $arr = array("apples", "bananas", "oranges", "strawberries"),
 - $obj = array();
 - $i = 10;
 - foreach($arr as $fruit){
 - $obj[$fruit] = $i;
 - $i += 10;
 - }
 - echo json_encode(obj);
 - ?>
 - <script>
 - $.ajax({
 - url:"/json.php",
 - success: function(data){
 - var x;
 - for(x in data){
 - alert("fruit:" + x + " points:" + data[x]);
 - }
 - }
 - });
 - </script>
 
最初,對(duì)于統(tǒng)一使用一種編程語(yǔ)言的嘗試是使用后臺(tái)的語(yǔ)言編寫客戶端的組件,然后編譯成JavaScript。但這種方式并沒(méi)有如期望的一樣很好地工作,許多相關(guān)的項(xiàng)目都失敗了(比如被ASP MVC取代了的ASP.NET Web forms, 又比如正在逐步被Polymer取代的GWT)。當(dāng)然這些想法都是偉大的,從本質(zhì)上講,都是想在服務(wù)器端和客戶端使用同一種語(yǔ)言,讓我們可以重用一些組件和資源(注意這里的關(guān)鍵詞:資源)。
最終得出的答案很簡(jiǎn)單:將JavaScript放到服務(wù)端
其實(shí)JavaScript誕生之初是在網(wǎng)景公司的企業(yè)及服務(wù)器的 服務(wù)端,只是當(dāng)時(shí)它還沒(méi)有完全準(zhǔn)備好。經(jīng)過(guò)數(shù)年的磨煉和錯(cuò)失,最終Node.js出現(xiàn)了,它不僅將JavaScript放到了服務(wù)器端,同時(shí)也推廣了非阻 塞式編程(non-blocking programming)的思想,這種思想來(lái)自于nginx的世界。感謝Node的創(chuàng)始者們nginx的技術(shù)背景,并且繼續(xù)(聰明地)保持了它的簡(jiǎn)單性, 也感謝JavaScript天生的事件輪詢機(jī)制。
#p#
(一句話概括,非阻塞式編程目的在于將消耗時(shí)間的任務(wù)放到一邊,通過(guò)指定在這些任務(wù)結(jié)束時(shí)需要做的操作,這樣可以在同一時(shí)刻讓處理器去處理其他的請(qǐng)求。)
Node.js永久性地改變了我們處理I/O訪問(wèn)的方式。作為Web開(kāi)發(fā)者,我們過(guò)去一直使用如下的方式訪問(wèn)數(shù)據(jù)庫(kù)(I/O):
- var resultset = db.query("SELECT * FROM 'table'");
 - drawTable(resultset);
 
這里的第一行代碼本質(zhì)上已經(jīng)阻塞了你的代碼,因?yàn)槟愕拇a停止下來(lái)等待數(shù)據(jù)庫(kù)驅(qū)動(dòng)返回一個(gè)結(jié)果集(resultset)。而與此同時(shí),你的平臺(tái)架構(gòu)其實(shí)給你提供了并發(fā)的方法,通常是通過(guò)線程(threads)和派生(forks)。
在Node.js和非阻塞式編程的幫助下,我們可以更多的控制我們程序的執(zhí)行流。現(xiàn)在(盡管在數(shù)據(jù)庫(kù)I/O驅(qū)動(dòng)器的背后可能已經(jīng)有并行執(zhí)行),你可以定義你的程序在I/O操作期間并行做的事情,以及在接收到結(jié)果集之后做的操作。
- db.query("SELECT * FROM 'table'", function(resultset){
 - drawTable(resultset);
 - });
 - doSomeThingElse();
 
上面的代碼片段中,我們定義了兩個(gè)程序流:第一個(gè)在我們發(fā)出數(shù)據(jù)庫(kù)查詢之后執(zhí)行的操作,第二個(gè)是以回調(diào)的方式在我們接收到結(jié)果集之后做的操作。這是一個(gè)非常優(yōu)雅并且強(qiáng)大的處理并發(fā)的方式。正如他們所說(shuō)的,“一切都在并行執(zhí)行——除了你的代碼。(Evetything runs in parallel — except your code.)”這樣,你的代碼會(huì)更易寫,有更高的可讀性,容易理解,也便于維護(hù),這些都基于你找回了對(duì)程序流的控制。
這些觀點(diǎn)早就不是很新的觀點(diǎn),那為什么他們隨著Node.js變得如此流行起來(lái)。很簡(jiǎn)單:非阻塞式編程可以有多重實(shí)現(xiàn)的方式。但可能最簡(jiǎn)單的就是使用回調(diào)和事件輪詢。在大多數(shù)于語(yǔ)言里,做到這點(diǎn)并不是一個(gè)簡(jiǎn)單的事情。回調(diào)機(jī)制在其他的一些于語(yǔ)言里是一個(gè)比較常見(jiàn)的功能,但是事件輪詢卻不是。你會(huì)經(jīng)常發(fā)現(xiàn)自己還需要在一些擴(kuò)展庫(kù)上做掙扎(比如,Python中使用Tornado)。
但是在JavaScript中,回調(diào)機(jī)制已經(jīng)被內(nèi)建在語(yǔ)言中, 事件輪詢也是如此。而對(duì)JavaScript稍有了解的程序員對(duì)它們也非常熟悉(或者至少使用過(guò)它們,即使他們有可能并不完全理解什么是事件輪詢)。突然之間,地球上所有的創(chuàng)業(yè)公司都可以在客戶端和服務(wù)器端重用開(kāi)發(fā)人員(或者資源),解決了“需要Python大師(Python Guru Needed)”的招聘發(fā)布問(wèn)題。
因此,現(xiàn)在我們有了一個(gè)發(fā)展迅速的平臺(tái)(感謝于非阻塞式編程),和一個(gè)非常易于使用的語(yǔ)言(感謝JavaScript)。但是這就足夠了嗎?它是可持續(xù)的嗎?我確信,JavaScript在將來(lái)會(huì)有一個(gè)非常重要的地位。下面我來(lái)告訴你為什么。
函數(shù)式編程
JavaScript是第一個(gè)將函數(shù)式范式帶給民眾的語(yǔ)言(當(dāng)然,Lisp第一個(gè)出現(xiàn),但是大多數(shù)的程序員都沒(méi)有使用它開(kāi)發(fā)過(guò)一個(gè)可以作為產(chǎn)品的應(yīng)用)。Lisp和Self,這兩個(gè)深深影響了JavaScript的語(yǔ)言,充滿了創(chuàng)新的理念,它們解放了我們的思想,去挖掘新的技術(shù),模式和規(guī)范。這些都延續(xù)到了JavaScript上??匆幌耺ondas, Church number, 或者甚至(作為更有實(shí)踐性的例子)Underscore的Collections functions,這些可以節(jié)約你一行又一行的代碼。
動(dòng)態(tài)對(duì)象以及原型繼承
沒(méi)有類(Classes),也沒(méi)有無(wú)窮無(wú)盡的類層次結(jié)構(gòu)的面向?qū)ο螅∣bject-oriented)編程是提供了更快速的編程體驗(yàn)——只要?jiǎng)?chuàng)建對(duì) 象,添加方法然后使用他們。更重要的是,它大大減少了維護(hù)時(shí)重構(gòu)的成本,因?yàn)樗试S程序員直接修改對(duì)象的實(shí)例,而不需要修改類。這種速度和靈活的方式為快 速開(kāi)發(fā)鋪平了道路。
JavaScript就是互聯(lián)網(wǎng)
JavaScript是因互聯(lián)網(wǎng)而生的。它從一開(kāi)始就出現(xiàn)了,并且伴隨到現(xiàn)在。任何想要摧毀它的嘗試都以失敗而告終,比如Java Applets的衰落,VBScript被微軟的TypeScript(它最終會(huì)被編譯成JavaScript)所取代,以及Flash在手機(jī)市場(chǎng)以及HTML5上的一敗涂地。如果想不破壞成千上萬(wàn)個(gè)Web頁(yè)面而取代JavaScript是不可能的,所以我們接下來(lái)的目標(biāo)應(yīng)該是提高和完善它。這個(gè)工作,沒(méi)有誰(shuí)比ECMA的Technical Committee 39更適合了。
當(dāng)然,JavaScript的替代者們每天都在誕生,比如CoffeeScript,TypeScript,以及成千上萬(wàn)能被編譯成JavaScript的語(yǔ)言。這些替代者們?cè)陂_(kāi)發(fā)過(guò)程中也許是有用的(通過(guò)source maps), 但是他們最終都不可能成功地代替JavaScript,兩個(gè)主要原因:他們的社區(qū)永遠(yuǎn)不會(huì)比JavaScript更大,他們中的優(yōu)秀特性會(huì)被 ECMAScript(也就是JavaScript)所吸收。JavaScript不是匯編語(yǔ)言,它是一個(gè)你能理解代碼的高級(jí)編程語(yǔ)言——所以你應(yīng)該理解 它。
端到端(End-to-End)JavaScript:Node.js和MongoDB
我們已經(jīng)介紹了為什么要使用JavaScript。接著,我們來(lái)看看使用Node.js和MongoDB的理由。
NODE.JS
Node.js是 一個(gè)搭建快速和可擴(kuò)展的網(wǎng)絡(luò)應(yīng)用的平臺(tái)——正如Node.js網(wǎng)站上所說(shuō)。但是Node.js遠(yuǎn)不止這些:它是如今最火的JavaScript運(yùn)行環(huán)境, 被大量的應(yīng)用和程序庫(kù)所使用——甚至是瀏覽器的庫(kù)代碼也運(yùn)行在Node.js上。更重要的是,這種服務(wù)器端的快速執(zhí)行讓程序員可以專注于更復(fù)雜的問(wèn)題,比 如做自然語(yǔ)言處理的Natural。即使你并沒(méi)有計(jì)劃用Node.js來(lái)寫你的服務(wù)器端應(yīng)用,你也有可能使用基于Node.js的工具來(lái)改進(jìn)你的開(kāi)發(fā)流程。舉例來(lái)說(shuō):用Bower來(lái)做前端包依賴管理,Mocha做單元測(cè)試,Grunt做自動(dòng)化打包,甚至用Brachets做全文代碼編輯。
因此,如果你正準(zhǔn)備開(kāi)發(fā)服務(wù)器端活客戶端的JavaScript應(yīng)用,你就需要對(duì)Node.js更加熟悉,因?yàn)槟阍谌粘9ぷ髦袝?huì)需要他。有一些很有趣的代替的選擇,但是它們中的任何一個(gè)的社區(qū)都不及Node.js的10%。
MONGODB
MongoDB是一個(gè)基于文檔(Document-based)NoSQL數(shù)據(jù)庫(kù),它使用JavaScript作為它的查詢語(yǔ)言(但是它不是用JavaScript寫的),它完善了我們端到端的JavaScript平臺(tái)。但是這個(gè)并不是我們選擇MonoDB的主要原因。
MongoDB 是無(wú)模式的(schema-less),允許你以非常靈活的方式把對(duì)象持久化,因此能夠迅速的應(yīng)對(duì)需求變更。此外,它具有高度可擴(kuò)展性,并且基于map-reduce,讓它非常適合于大數(shù)據(jù)的應(yīng)用。MongoDB如此靈活,以至于它既可以用作無(wú)模式的文檔數(shù)據(jù)庫(kù),也可以用作關(guān)系數(shù)據(jù)存儲(chǔ)(盡管它缺少事務(wù),只能通過(guò)模擬來(lái)實(shí)現(xiàn)),甚至是用來(lái)緩存結(jié)果的鍵值對(duì)存儲(chǔ),就像Memcached和Redis。
#p#
基于Express的服務(wù)器端組件化
服務(wù)器端的組件化開(kāi)發(fā)一直不是一件容易的是。但是 Express(和Connect)帶來(lái)了“中間件(middleware)的思想”。在我看來(lái),中間件是服務(wù)器端定義組件最好的方式。如果你想找個(gè)熟悉 的模式來(lái)對(duì)比一下的話,那它非常接近于管道和過(guò)濾器(pipes and filters)。
基本思想就是將你的組件作為管道的一部分。管道處理一個(gè)請(qǐng)求(也叫輸入),生成一個(gè)結(jié)果(也叫輸出),但是你的組件并不負(fù)責(zé)整個(gè)響應(yīng)結(jié)果。相反,它只做它需要做的修改,然后將委派給下管道的下一節(jié)點(diǎn)。當(dāng)管道的最后的節(jié)點(diǎn)處理完之后,這個(gè)結(jié)果再返回給客戶端。
我們稱這些管道的節(jié)點(diǎn)為中間件。很明顯,我們可以創(chuàng)建兩種類型的中間件:
- 中間型(Intermediates)
 - 一個(gè)中間型節(jié)中間件理請(qǐng)求和響應(yīng),但是它不負(fù)全權(quán)責(zé)整個(gè)響應(yīng),而是繼續(xù)將它們分派給下一個(gè)中間件。
 - 終結(jié)型(Finals)
 - 一個(gè)結(jié)束型中間件負(fù)責(zé)最終的響應(yīng)結(jié)果。它對(duì)請(qǐng)求和響應(yīng)進(jìn)行處理,之后不會(huì)分派給下一個(gè)中間件。但實(shí)踐中,繼續(xù)分派給一個(gè)中間件可以給架構(gòu)帶來(lái)更高的靈活性(比如,之后需要增加其他的中間件),即使下一個(gè)中間件并不存在(這種情況下,結(jié)果會(huì)直接被傳遞到客戶端)。
 
取一個(gè)具體的例子,假設(shè)服務(wù)器端有一個(gè)“用戶管理”的組件。根據(jù)中間件的方式,我們最好能有終結(jié)型和中間型的中間件。對(duì)于終結(jié)節(jié)點(diǎn),我們要有創(chuàng)建用 戶和列出用戶的功能。但是在我們做這些操作之前,我們需要使用中間節(jié)點(diǎn)來(lái)做認(rèn)證(因?yàn)槲覀儾幌M麤](méi)有認(rèn)證過(guò)的請(qǐng)求能進(jìn)來(lái),甚至創(chuàng)建用戶)。一旦我們創(chuàng)建好 了這些認(rèn)證中間件,當(dāng)我們想要把一個(gè)原先不需要認(rèn)證的功能改變成認(rèn)證功能的時(shí)候,我們只需要將這個(gè)中間件安插在相應(yīng)的位置。
單頁(yè)面(Single-Page)應(yīng)用
當(dāng)你使用全棧式JavaScript的時(shí)候,多數(shù)情況下你會(huì)專注開(kāi)發(fā)單頁(yè)面應(yīng)用。大多數(shù)的Web開(kāi)發(fā)者們都禁不住不止一次地嘗試著著手于單頁(yè)面應(yīng)用。我已經(jīng)創(chuàng)建了幾個(gè)(多數(shù)為個(gè)人的),我相信他們就是Web應(yīng)用的未來(lái)。你是否在移動(dòng)鏈接上對(duì)比過(guò)單頁(yè)面應(yīng)用和通常的Web應(yīng)用?他們?cè)陧憫?yīng)速度的差距有數(shù)十秒之多。
(注意:有些人可能不同意我的觀點(diǎn)。比如Twitter,回滾了他們的單頁(yè)面途徑。與此同時(shí),很多大的網(wǎng)站正在步入單頁(yè)面時(shí)代,比如Zendesk。我已經(jīng)看到足夠的證據(jù)證明單頁(yè)面應(yīng)用帶來(lái)的好處,并且對(duì)此深信不疑。但是具體還是因情況而異。)
如果單頁(yè)面應(yīng)用如此強(qiáng)大,那為什么還是要選擇老土的方式來(lái)創(chuàng)建你的應(yīng)用呢?我經(jīng)常聽(tīng)到的一種爭(zhēng)論就是他們擔(dān)心SEO(Search Engine Optimization)。但是如果你對(duì)此做了正確的處理,這將不是一個(gè)問(wèn)題:你可以有多種解決方式,從使用無(wú)界面的瀏覽器(headless browser),比如PhantomJS,在檢測(cè)到網(wǎng)絡(luò)爬蟲的時(shí)候渲染HTML,到使用一些現(xiàn)有框架執(zhí)行服務(wù)器端渲染。
基于Backbones.js,Marionette和Twitter Bootstrap的客戶端MV*模式
關(guān)于使用MV*框架開(kāi)發(fā)單頁(yè)面應(yīng)用已經(jīng)有太多的討論了。盡管很難選擇,但是我想說(shuō)排名前三的是Backbone.js, Ember和AngularJS。
這三個(gè)都是非常被推崇的,但哪個(gè)是最適合你的?
不幸的是,我必須得承認(rèn)我在AngularJS上的經(jīng)驗(yàn)有限,所以我就把它放在討論范圍之外。那么,Ember和Backbone.js代表了解決同一問(wèn)題的兩種不同方式。
Backbone.js很小,但是恰到好處的提供了創(chuàng)建一個(gè)簡(jiǎn)單的單頁(yè)面應(yīng)用所需要的功能。另一方面,Ember是一個(gè)創(chuàng)建單頁(yè)面應(yīng)用的完整且專業(yè)的框架。它有更多的輔助工具,但是也有更加陡峭的學(xué)習(xí)曲線。(你可以閱讀更多關(guān)于Ember.js的內(nèi)容。)
基于你的應(yīng)用的大小,可以簡(jiǎn)單地通過(guò)比較“需要的功能”占“可用的功能”的比例來(lái)做出決定,它會(huì)給你很大的提示。
樣式設(shè)計(jì)也同樣是一個(gè)挑戰(zhàn),但是再次,我們也可以列舉出一些可以助我們一臂之力的框架。對(duì)于CSS,Twitter Bootstrap是一個(gè)非常好的選擇,它提供了一套完整的樣式,它們可以立即使用,也非常便于自定義。
Bootstrap是使用LESS語(yǔ)言創(chuàng)建的,它是開(kāi)源的,我們可以根據(jù)我們的需要來(lái)修改它。伴隨它的還有一大堆用戶友好的組件,它們也有非常完善的文檔。此外,一個(gè)定制化模式讓你很方便地創(chuàng)建你自己的。毫無(wú)疑問(wèn),它正是這個(gè)工作所需要的正確的工具。
最佳實(shí)踐:Grunt,Mocha,Chai,RequireJS 和 CoverJS
最后,我們將定義一些最佳實(shí)踐,同時(shí)談?wù)勗撊绾螌?shí)現(xiàn)和維護(hù)它們。具有代表性的,我的解決方案,最終聚焦到幾個(gè)工具上,他們本身都是基于Node.js。
MOCHA 和 CHAI
這些工具能幫助你使用測(cè)試驅(qū)動(dòng)開(kāi)發(fā)模式(test-driven development)或者行為驅(qū)動(dòng)開(kāi)發(fā)模式(behavior-driven development)來(lái)改進(jìn)你的開(kāi)發(fā)流程,創(chuàng)建一些基礎(chǔ)架構(gòu)來(lái)管理你的單元測(cè)試,并且自動(dòng)運(yùn)行這些測(cè)試。
現(xiàn)在有大量的JavaScript單元測(cè)試框架,為什么要用Mocha?簡(jiǎn)短的回答就是它即靈活又完善。我來(lái)解釋一下:
- 用戶界面(Interfaces)
 - 也許你習(xí)慣于測(cè)試驅(qū)動(dòng)的程序組和單元測(cè)試的概念,又或許傾向于行為驅(qū)動(dòng)測(cè)試的使用describle和should來(lái)定義行為定義的理念。Mocha讓你可以同時(shí)使用這兩種方式。
 - 報(bào)表生成器(reporter)
 - 運(yùn)行你的測(cè)試代碼會(huì)生成測(cè)試結(jié)果的報(bào)表,你可以使用各式各樣的reporter來(lái)格式化這些結(jié)果。舉例來(lái)說(shuō),如果你需要提供一個(gè)持續(xù)集成服務(wù)器信息,你可以找到一個(gè)report來(lái)做這些。
 - 沒(méi)有指定斷言庫(kù)(Lack of an assertion library)
 - 這幾乎不是一個(gè)問(wèn)題,Mocha決定讓你選擇自己要使用的斷言庫(kù),從而給你更多的靈活性。你有很多的選擇,這正是Chai施展身手的地方。
 
Chai 是一個(gè)非常靈活的斷言庫(kù),它可以讓你使用如下三中主要斷言方式的任何一種:
- assert
 
這是來(lái)自老派測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的經(jīng)典的assert方式。比如:
- assert.equal(variable, "value");
 
- expect
 
這種鏈?zhǔn)降臄嘌燥L(fēng)格在行為驅(qū)動(dòng)開(kāi)發(fā)中最為常見(jiàn)。比如:
- expect(variable).to.equal("value");
 
should
這也是用在測(cè)試驅(qū)動(dòng)開(kāi)發(fā)中,但是我更推薦expect,因?yàn)閟hould經(jīng)常聽(tīng)起來(lái)比較反復(fù)(比如,定義一個(gè)行為規(guī)范,”it (should do something…)”)。舉例:
- variable.should.equal("value");
 
Chai和Mocha可以無(wú)縫集成。使用這兩個(gè)程序庫(kù),你可以使用測(cè)試驅(qū)動(dòng),行為驅(qū)動(dòng)活任何想得到的方式來(lái)寫你的測(cè)試代碼。
#p#
GRUNT
Grunt是你能夠自動(dòng)化你的build任務(wù),包含簡(jiǎn)單的復(fù)制粘貼和文件拼接,模板預(yù)編譯,style語(yǔ)言(SASS和LESS)編譯,單元測(cè)試(使用Mocha),代碼檢查,以及代碼最小化(比如,使用UglifyJS或者Closure Compiler)。你可以添加你自己的自動(dòng)化任務(wù)到Grunt中或者搜索registry,那里數(shù)百個(gè)插件可供使用(再次提醒,選擇使用有良好的社區(qū)支持的工具)。Grunt也可以監(jiān)控你的文件,當(dāng)發(fā)生更改時(shí)觸發(fā)一些操作。
REQUIREJS
RequireJS 聽(tīng)起來(lái)是基于AMD API的另一種加載模塊的方式,但是我敢保證地告訴你,它遠(yuǎn)遠(yuǎn)不止這個(gè)功能。使用RequireJS,你可以定義你的模塊之間的依賴和層次結(jié)構(gòu),讓 RequireJS庫(kù)幫你來(lái)加載他們。它還提供了一種非常簡(jiǎn)便的方式來(lái)避免全局變量污染,通過(guò)在函數(shù)體中定義你的模塊。這讓模塊可以重用,不像命名空間模塊(namespaced modules)。試想一下:如果定義了一個(gè)類似于Demoapp.helloWorlModule的模塊,你想把他改成Firstapp.helloWorldModule,那么你需要把所有引用到Demoapp命名空間的地方都做修改,才能讓它變得可移植。
RequireJS還能讓你擁抱依賴注入模式。假設(shè)你有一個(gè)模塊需要用到主應(yīng)用對(duì)象(單例)的一個(gè)實(shí)例。通過(guò)使用RequireJS,你意識(shí)到你不 需要使用全局變量來(lái)存儲(chǔ)它,你也不能使用一個(gè)實(shí)例作為RequireJS的依賴。所以,你需要在你的模塊構(gòu)造器中加載這個(gè)依賴。讓我們看一個(gè)例子:
在main.js:
- define(
 - ["App","module"],
 - function(App, Module){
 - var app = new App();
 - var module = new Module({
 - app: app
 - })
 - return app;
 - }
 - );
 
在module.js:
- define([],
 - function(){
 - var module = function(options){
 - this.app = options.app;
 - };
 - module.prototype.useApp = function(){
 - this.app.performAction();
 - };
 - return module
 - }
 - );
 
注意,我們不能在module的定義中加入對(duì)main.js的依賴,否則我們會(huì)創(chuàng)建出一個(gè)循環(huán)引用。
COVERJS
代碼覆蓋率(Code coverage)是你測(cè)試的一個(gè)度量標(biāo)準(zhǔn)。正如它的名字所示,它能告訴你當(dāng)前的測(cè)試集覆蓋了你代碼的多少部分。CoverJS通過(guò)檢測(cè)你代碼中的語(yǔ)句 (而不是像JSCoverage那樣看代碼行)并生成一個(gè)檢測(cè)過(guò)的版本的代碼來(lái)測(cè)量你的測(cè)試代碼的覆蓋率。它也可以支持對(duì)持續(xù)集成服務(wù)器提供持續(xù)報(bào)表生 成。
總結(jié)
全棧式JavaScript并不能解決所有的問(wèn)題。但是它的社區(qū)和技術(shù)會(huì)帶領(lǐng)你走很長(zhǎng)一段路。使用JavaScript,你可以創(chuàng)建基于統(tǒng)一的語(yǔ)言的可擴(kuò)展的,可維護(hù)的應(yīng)用。毫無(wú)疑問(wèn),這是絕對(duì)值得我們關(guān)注的。
















 
 
 







 
 
 
 