全棧式JavaScript

  現在,在建立一個Web應用的過程當中,你須要作出許多架構方面的決策。固然,你會但願作的每個決定都是正確的:你想要使用可以快速開發的技術,支持持續的迭代,最高的工做效率,迅速,健壯性強。你想要精益求精而且足夠敏捷。你但願你選擇的技術可以在短時間和長期上都讓你的項目取得成功。但這些技術都不是垂手可得就能選出來的。javascript

  個人經驗告訴我,全棧式JavaScript符合了這全部的要求。可能你已經發現了些許端倪,又或許你已經在考慮它的實用性,而且在和朋友討論爭論它的話題。可是你是否親自嘗試過呢?在這篇文章中,我會對於全棧式JavaScript給出一個比較全面的介紹,爲何它會是正確的選擇,它又是如何施展它的魔法的。php

  先給出一個歸納預覽:css

toptal-blog-500-opt

  接下來我會一項一項地介紹這些組件。可是在這以前,咱們簡短地回顧一下,咱們是如何發展到如今的這個階段的。html

  我爲何選擇用JavaScript

  從1998年開始,我就是一個Web開發者。當時,咱們使用Perl進行大多數的服務器端的開發;可是從那時候開始,咱們就在客戶端使用JavaScript。Web服務器端的技術已經發生了翻天覆地的變化:咱們被一波又一波的技術潮流推着往前走,PHP,ASP,JSP,.NET,Ruby,Python,這裏只列出了幾個例子。開發人員們開始意識到,在服務器端和客戶端使用不一樣的語言使得事情變得複雜化。前端

  在早期的PHP和ASP的時代,那個時候模板引擎還僅僅是個設想,開發人員們在HTML中嵌入他們的應用代碼。咱們常常能夠看到下面這種腳本嵌入的寫法:html5

1
2
3
4
5
6
7
8
9
<script>
     <?php
         if ($login == true ){
     ?>
     alert( "Welcome" );
     <?php
         }
     ?>
</script>

  或者更糟糕:java

1
2
3
4
5
6
7
8
9
10
11
<script>
     var users_deleted = [];
     <?php
         $arr_ids = array( 1 , 2 , 3 , 4 );
         foreach($arr_ids as $value){
     ?>
     users_deleted.push( "<php>" );
     <?php
         }
     ?>
</script>

  對於新手來講,很容易被不一樣語言之間的用法而混淆,犯下一些很典型的錯誤,好比for和foreach。更爲不爽的是,以這樣的方式來寫代碼,使得服務器端和客戶端很難以很是和諧的方式處理相同的數據結構,即便是今天也是如此(固然除非你的開發團隊有專職的前端和後端工程師 — 但即便他們之間可以共享信息,但仍然不能僅僅基於對方的代碼進行合做)。node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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>

  最初,對於統一使用一種編程語言的嘗試是使用後臺的語言編寫客戶端的組件,而後編譯成JavaScript。但這種方式並無如指望的同樣很好地工做,許多相關的項目都失敗了(好比被ASP MVC取代了的ASP.NET Web forms, 又好比正在逐步被Polymer取代的GWT)。固然這些想法都是偉大的,從本質上講,都是想在服務器端和客戶端使用同一種語言,讓咱們能夠重用一些組件和資源(注意這裏的關鍵詞:資源)。nginx

  最終得出的答案很簡單:將JavaScript放到服務端git

  其實JavaScript誕生之初是在網景公司的企業及服務器的服務端,只是當時它尚未徹底準備好。通過數年的磨鍊和錯失,最終Node.js出現了,它不只將JavaScript放到了服務器端,同時也推廣了非阻塞式編程(non-blocking programming)的思想,這種思想來自於nginx的世界。感謝Node的創始者們nginx的技術背景,而且繼續(聰明地)保持了它的簡單性,也感謝JavaScript天生的事件輪詢機制。

  (一句話歸納,非阻塞式編程目的在於將消耗時間的任務放到一邊,經過指定在這些任務結束時須要作的操做,這樣能夠在同一時刻讓處理器去處理其餘的請求。)

  Node.js永久性地改變了咱們處理I/O訪問的方式。做爲Web開發者,咱們過去一直使用以下的方式訪問數據庫(I/O):

1
2
var resultset = db.query( "SELECT * FROM 'table'" );
drawTable(resultset);

  這裏的第一行代碼本質上已經阻塞了你的代碼,由於你的代碼中止下來等待數據庫驅動返回一個結果集(resultset)。而與此同時,你的平臺架構其實給你提供了併發的方法,一般是經過線程(threads)和派生(forks)。

  在Node.js和非阻塞式編程的幫助下,咱們能夠更多的控制咱們程序的執行流。如今(儘管在數據庫I/O驅動器的背後可能已經有並行執行),你能夠定義你的程序在I/O操做期間並行作的事情,以及在接收到結果集以後作的操做。

1
2
3
4
db.query( "SELECT * FROM 'table'" , function (resultset){
    drawTable(resultset);
});
doSomeThingElse();

  上面的代碼片斷中,咱們定義了兩個程序流:第一個在咱們發出數據庫查詢以後執行的操做,第二個是以回調的方式在咱們接收到結果集以後作的操做。這是一個很是優雅而且強大的處理併發的方式。正如他們所說的,「一切都在並行執行——除了你的代碼。(Evetything runs in parallel — except your code.)」這樣,你的代碼會更易寫,有更高的可讀性,容易理解,也便於維護,這些都基於你找回了對程序流的控制。

  這些觀點早就不是很新的觀點,那爲何他們隨着Node.js變得如此流行起來。很簡單:非阻塞式編程能夠有多重實現的方式。但可能最簡單的就是使用回調和事件輪詢。在大多數於語言裏,作到這點並非一個簡單的事情。回調機制在其餘的一些於語言裏是一個比較常見的功能,可是事件輪詢卻不是。你會常常發現本身還須要在一些擴展庫上作掙扎(好比,Python中使用Tornado)。

  可是在JavaScript中,回調機制已經被內建在語言中, 事件輪詢也是如此。而對JavaScript稍有了解的程序員對它們也很是熟悉(或者至少使用過它們,即便他們有可能並不徹底理解什麼是事件輪詢)。忽然之間,地球上全部的創業公司均可以在客戶端和服務器端重用開發人員(或者資源),解決了「須要Python大師(Python Guru Needed)」的招聘發佈問題

  所以,如今咱們有了一個發展迅速的平臺(感謝於非阻塞式編程),和一個很是易於使用的語言(感謝JavaScript)。可是這就足夠了嗎?它是可持續的嗎?我確信,JavaScript在未來會有一個很是重要的地位。下面我來告訴你爲何。

  函數式編程

  JavaScript是第一個將函數式範式帶給民衆的語言(固然,Lisp第一個出現,可是大多數的程序員都沒有使用它開發過一個能夠做爲產品的應用)。Lisp和Self,這兩個深深影響了JavaScript的語言,充滿了創新的理念,它們解放了咱們的思想,去挖掘新的技術,模式和規範。這些都延續到了JavaScript上。看一下mondasChurch number, 或者甚至(做爲更有實踐性的例子)UnderscoreCollections functions,這些能夠節約你一行又一行的代碼。

  動態對象以及原型繼承

  沒有類(Classes),也沒有無窮無盡的類層次結構的面向對象(Object-oriented)編程是提供了更快速的編程體驗——只要建立對象,添加方法而後使用他們。更重要的是,它大大減小了維護時重構的成本,由於它容許程序員直接修改對象的實例,而不須要修改類。這種速度和靈活的方式爲快速開發鋪平了道路。

  JavaScript就是互聯網

  JavaScript是因互聯網而生的。它從一開始就出現了,而且伴隨到如今。任何想要摧毀它的嘗試都以失敗而了結,好比Java Applets的衰落,VBScript被微軟的TypeScript(它最終會被編譯成JavaScript)所取代,以及Flash在手機市場以及HTML5上的一敗塗地。若是想不破壞成千上萬個Web頁面而取代JavaScript是不可能的,因此咱們接下來的目標應該是提升和完善它。這個工做,沒有誰比ECMA的Technical Committee 39更適合了。

  固然,JavaScript的替代者們天天都在誕生,好比CoffeeScriptTypeScript,以及成千上萬能被編譯成JavaScript的語言。這些替代者們在開發過程當中也許是有用的(經過source maps),可是他們最終都不可能成功地代替JavaScript,兩個主要緣由:他們的社區永遠不會比JavaScript更大,他們中的優秀特性會被ECMAScript(也就是JavaScript)所吸取。JavaScript不是彙編語言,它是一個你能理解代碼的高級編程語言——因此你應該理解它。

  端到端(End-to-End)JavaScript:Node.js和MongoDB

  咱們已經介紹了爲何要使用JavaScript。接着,咱們來看看使用Node.js和MongoDB的理由。

  NODE.JS

  Node.js是一個搭建快速和可擴展的網絡應用的平臺——正如Node.js網站上所說。可是Node.js遠不止這些:它是現在最火的JavaScript運行環境,被大量的應用和程序庫所使用——甚至是瀏覽器的庫代碼也運行在Node.js上。更重要的是,這種服務器端的快速執行讓程序員能夠專一於更復雜的問題,好比作天然語言處理Natural。即便你並無計劃用Node.js來寫你的服務器端應用,你也有可能使用基於Node.js的工具來改進你的開發流程。舉例來講:用Bower來作前端包依賴管理,Mocha作單元測試,Grunt作自動化打包,甚至用Brachets作全文代碼編輯。

  所以,若是你正準備開發服務器端活客戶端的JavaScript應用,你就須要對Node.js更加熟悉,由於你在平常工做中會須要他。有一些頗有趣的代替的選擇,可是它們中的任何一個的社區都不及Node.js的10%。

  MONGODB

  MongoDB是一個基於文檔(Document-based)NoSQL數據庫,它使用JavaScript做爲它的查詢語言(可是它不是用JavaScript寫的),它完善了咱們端到端的JavaScript平臺。可是這個並非咱們選擇MonoDB的主要緣由。

  MongoDB 是無模式的(schema-less),容許你以很是靈活的方式把對象持久化,所以可以迅速的應對需求變動。此外,它具備高度可擴展性,而且基於map-reduce,讓它很是適合於大數據的應用。MongoDB如此靈活,以致於它既能夠用做無模式的文檔數據庫,也能夠用做關係數據存儲(儘管它缺乏事務,只能經過模擬來實現),甚至是用來緩存結果的鍵值對存儲,就像MemcachedRedis

  基於Express的服務器端組件化

  服務器端的組件化開發一直不是一件容易的是。可是 Express(和Connect)帶來了「中間件(middleware)的思想」。在我看來,中間件是服務器端定義組件最好的方式。若是你想找個熟悉的模式來對比一下的話,那它很是接近於管道和過濾器(pipes and filters)。

  基本思想就是將你的組件做爲管道的一部分。管道處理一個請求(也叫輸入),生成一個結果(也叫輸出),可是你的組件並不負責整個響應結果。相反,它只作它須要作的修改,而後將委派給下管道的下一節點。當管道的最後的節點處理完以後,這個結果再返回給客戶端。

  咱們稱這些管道的節點爲中間件。很明顯,咱們能夠建立兩種類型的中間件:

  • 中間型(Intermediates)
    一箇中間型節中間件理請求和響應,可是它不負全權責整個響應,而是繼續將它們分派給下一個中間件。
  • 終結型(Finals)
    一個結束型中間件負責最終的響應結果。它對請求和響應進行處理,以後不會分派給下一個中間件。但實踐中,繼續分派給一箇中間件能夠給架構帶來更高的靈活性(好比,以後須要增長其餘的中間件),即便下一個中間件並不存在(這種狀況下,結果會直接被傳遞到客戶端)。

user-manager-500-opt
(Large view)

  取一個具體的例子,假設服務器端有一個「用戶管理」的組件。根據中間件的方式,咱們最好能有終結型和中間型的中間件。對於終結節點,咱們要有建立用戶和列出用戶的功能。可是在咱們作這些操做以前,咱們須要使用中間節點來作認證(由於咱們不但願沒有認證過的請求能進來,甚至建立用戶)。一旦咱們建立好了這些認證中間件,當咱們想要把一個原先不須要認證的功能改變成認證功能的時候,咱們只須要將這個中間件安插在相應的位置。

  單頁面(Single-Page)應用

  當你使用全棧式JavaScript的時候,多數狀況下你會專一開發單頁面應用。大多數的Web開發者們都禁不住不止一次地嘗試着着手於單頁面應用。我已經建立了幾個(多數爲我的的),我相信他們就是Web應用的將來。你是否在移動連接上對比過單頁面應用和一般的Web應用?他們在響應速度的差距有數十秒之多。

  (注意:有些人可能不一樣意個人觀點。好比Twitter,回滾了他們的單頁面途徑。與此同時,不少大的網站正在步入單頁面時代,好比Zendesk。我已經看到足夠的證據證實單頁面應用帶來的好處,而且對此深信不疑。可是具體仍是因狀況而異。)

  若是單頁面應用如此強大,那爲何仍是要選擇老土的方式來建立你的應用呢?我常常聽到的一種爭論就是他們擔憂SEO(Search Engine Optimization)。可是若是你對此作了正確的處理,這將不是一個問題:你能夠有多種解決方式,從使用無界面的瀏覽器(headless browser),好比PhantomJS,在檢測到網絡爬蟲的時候渲染HTML,到使用一些現有框架執行服務器端渲染

  基於Backbones.js,Marionette和Twitter Bootstrap的客戶端MV*模式

  關於使用MV*框架開發單頁面應用已經有太多的討論了。儘管很難選擇,可是我想說排名前三的是Backbone.jsEmberAngularJS

  這三個都是很是被推崇的,但哪一個是最適合你的

  不幸的是,我必須得認可我在AngularJS上的經驗有限,因此我就把它放在討論範圍以外。那麼,Ember和Backbone.js表明瞭解決同一問題的兩種不一樣方式。

  Backbone.js很小,可是恰到好處的提供了建立一個簡單的單頁面應用所須要的功能。另外一方面,Ember是一個建立單頁面應用的完整且專業的框架。它有更多的輔助工具,可是也有更加陡峭的學習曲線。(你能夠閱讀更多關於Ember.js的內容。)

  基於你的應用的大小,能夠簡單地經過比較「須要的功能」佔「可用的功能」的比例來作出決定,它會給你很大的提示。

  樣式設計也一樣是一個挑戰,可是再次,咱們也能夠列舉出一些能夠助咱們一臂之力的框架。對於CSS,Twitter Bootstrap是一個很是好的選擇,它提供了一套完整的樣式,它們能夠當即使用,也很是便於自定義

  Bootstrap是使用LESS語言建立的,它是開源的,咱們能夠根據咱們的須要來修改它。伴隨它的還有一大堆用戶友好的組件,它們也有很是完善的文檔。此外,一個定製化模式讓你很方便地建立你本身的。毫無疑問,它正是這個工做所須要的正確的工具。

  最佳實踐:Grunt,Mocha,Chai,RequireJS 和 CoverJS

  最後,咱們將定義一些最佳實踐,同時談談該如何實現和維護它們。具備表明性的,個人解決方案,最終聚焦到幾個工具上,他們自己都是基於Node.js。

  MOCHA 和 CHAI

  這些工具能幫助你使用測試驅動開發模式(test-driven development)或者行爲驅動開發模式(behavior-driven development)來改進你的開發流程,建立一些基礎架構來管理你的單元測試,而且自動運行這些測試。

  如今有大量的JavaScript單元測試框架,爲何要用Mocha?簡短的回答就是它即靈活又完善。我來解釋一下:

  • 用戶界面(Interfaces)
    也許你習慣於測試驅動的程序組和單元測試的概念,又或許傾向於行爲驅動測試的使用describle和should來定義行爲定義的理念。Mocha讓你能夠同時使用這兩種方式。
  • 報表生成器(reporter)
    運行你的測試代碼會生成測試結果的報表,你可使用各式各樣的reporter來格式化這些結果。舉例來講,若是你須要提供一個持續集成服務器信息,你能夠找到一個report來作這些。
  • 沒有指定斷言庫(Lack of an assertion library)
    這幾乎不是一個問題,Mocha決定讓你選擇本身要使用的斷言庫,從而給你更多的靈活性。你有不少的選擇,這正是Chai施展身手的地方。

  Chai 是一個很是靈活的斷言庫,它可讓你使用以下三中主要斷言方式的任何一種:

  • assert
    這是來自老派測試驅動開發的經典的assert方式。好比:

     

    1
    assert.equal( var iable, "value" );
  • expect
    這種鏈式的斷言風格在行爲驅動開發中最爲常見。好比:

     

    1
    expect( var iable).to.equal( "value" );
  • should
    這也是用在測試驅動開發中,可是我更推薦expect,由於should常常聽起來比較反覆(好比,定義一個行爲規範,」it (should do something…)」)。舉例:

     

    1
    var iable.should.equal( "value" );

  Chai和Mocha能夠無縫集成。使用這兩個程序庫,你可使用測試驅動,行爲驅動活任何想獲得的方式來寫你的測試代碼。

  GRUNT

  Grunt是你可以自動化你的build任務,包含簡單的複製粘貼和文件拼接,模板預編譯,style語言(SASS和LESS)編譯,單元測試(使用Mocha),代碼檢查,以及代碼最小化(好比,使用UglifyJS或者Closure Compiler)。你能夠添加你本身的自動化任務到Grunt中或者搜索registry,那裏數百個插件可供使用(再次提醒,選擇使用有良好的社區支持的工具)。Grunt也能夠監控你的文件,當發生更改時觸發一些操做。

  REQUIREJS

  RequireJS 聽起來是基於AMD API的另外一種加載模塊的方式,可是我敢保證地告訴你,它遠遠不止這個功能。使用RequireJS,你能夠定義你的模塊之間的依賴和層次結構,讓RequireJS庫幫你來加載他們。它還提供了一種很是簡便的方式來避免全局變量污染,經過在函數體中定義你的模塊。這讓模塊能夠重用,不像命名空間模塊(namespaced modules)。試想一下:若是定義了一個相似於Demoapp.helloWorlModule的模塊,你想把他改爲Firstapp.helloWorldModule,那麼你須要把全部引用到Demoapp命名空間的地方都作修改,才能讓它變得可移植。

  RequireJS還能讓你擁抱依賴注入模式。假設你有一個模塊須要用到主應用對象(單例)的一個實例。經過使用RequireJS,你意識到你不須要使用全局變量來存儲它,你也不能使用一個實例做爲RequireJS的依賴。因此,你須要在你的模塊構造器中加載這個依賴。讓咱們看一個例子:

  在main.js:

1
2
3
4
5
6
7
8
9
10
11
12
define(
       [ "App" , "module" ],
       function (App, Module){
           var app = new App();
 
           var module = new Module({
               app: app
           })
 
           return app;
       }
   );

  在module.js

1
2
3
4
5
6
7
8
9
10
11
define([],
       function (){
           var module = function (options){
               this .app = options.app;
           };
           module.prototype.useApp = function (){
               this .app.performAction();
           };
           return module
       }
   );

  注意,咱們不能在module的定義中加入對main.js的依賴,不然咱們會建立出一個循環引用。

  COVERJS

  代碼覆蓋率(Code coverage)是你測試的一個度量標準。正如它的名字所示,它能告訴你當前的測試集覆蓋了你代碼的多少部分。CoverJS經過檢測你代碼中的語句(而不是像JSCoverage那樣看代碼行)並生成一個檢測過的版本的代碼來測量你的測試代碼的覆蓋率。它也能夠支持對持續集成服務器提供持續報表生成。

  總結

  全棧式JavaScript並不能解決全部的問題。可是它的社區和技術會帶領你走很長一段路。使用JavaScript,你能夠建立基於統一的語言的可擴展的,可維護的應用。毫無疑問,這是絕對值得咱們關注的。

  原文連接: smashingmagazine   翻譯: 伯樂在線 - Owen Chen

相關文章
相關標籤/搜索