本篇文檔的整理源自 www.jianshu.com/p/bf187fed8… 本書中的代碼案例都在Node.js 0.6.11版本中測試過,能夠正確工做。 讀完本書以後,你將完成一個完整的web應用,該應用容許用戶瀏覽頁面以及上傳文件。html
本書先從介紹在Node.js環境中進行JavaScript開發和在瀏覽器環境中進行JavaScript開發的差別開始。node
緊接着,會帶領你們完成一個最傳統的「Hello World」應用,這也是最基礎的Node.js應用。git
最後,會和你們討論如何設計一個「真正」完整的應用,剖析要完成該應用須要實現的不一樣模塊,並一步一步介紹如何來實現這些模塊。web
能夠確保的是,在這過程當中,你們會學到JavaScript中一些高級的概念、如何使用它們以及爲何使用這些概念就能夠實現而其餘編程語言中同類的概念就沒法實現。 該應用全部的源代碼均可以經過 Github代碼倉庫查看,你也能夠選擇先將代碼clone以後再繼續進行下面的閱讀。編程
1、本文檔結構
2、JavaScript與Node.js
- JavaScript與你
- 簡短申明
- 服務器端JavaScript
- 「Hello World」
3、一個完整的基於Node.js的web應用
- 用例
- 應用不一樣模塊分析
4、構建應用的模塊
- 一個基礎的HTTP服務器
- 分析HTTP服務器
- 進行函數傳遞
- 函數傳遞是如何讓HTTP服務器工做的
- 基於事件驅動的回調
- 服務器是如何處理請求的
- 服務端的模塊放在哪裏
- 如何來進行請求的「路由」
- 行爲驅動執行
- 路由給真正的請求處理程序
- 讓請求處理程序做出響應
①.很差的實現方式 ②.阻塞與非阻塞 ③.以非阻塞操做進行請求響應後端
5、更有用的場景
- 處理POST請求
- 處理文件上傳
6、總結與展望
Node.js,服務端的JavaScript。----從新認識JavaScript。瀏覽器
本書並非一本「從入門到精通」的書,更像是一本「從初級入門到高級入門」的書。 若是成功的話,那麼本書就是咱們開始學習Node.js最但願擁有的教程。服務器
要實如今後臺運行JavaScript代碼,代碼須要先被解釋而後正確的執行。Node.js的原理正是如此,它使用了Google的V8虛擬機 (Google的Chrome瀏覽器使用的JavaScript執行環境),來解釋和執行JavaScript代碼。 Node.js事實上既是一個運行時環境,同時又是一個庫 ####4."Hello World" 打開你最喜歡的編輯器,建立一個helloworld.js文件。輸出「Hello World」,以下是實現該功能的代碼:網絡
console.log("Hello World");
複製代碼
保存該文件,並經過Node.js來執行:app
node helloworld.js
複製代碼
正常的話,就會在終端輸出Hello World 。 第二部分結束。
咱們來把目標設定得簡單點,不過也要夠實際才行:
- 用戶能夠經過瀏覽器使用咱們的應用。
- 當用戶請求*http://domain/start*時,能夠看到一個歡迎頁面,頁面上有一個文件上傳的表單。
- 用戶能夠選擇一個圖片並提交表單,隨後文件將被上傳到*http://domain/upload*,該頁面完成上傳後會把圖片顯示在頁面上。
差很少了,你如今也能夠去Google一下,找點東西亂搞一下來完成功能。可是咱們如今先不作這個。
更進一步地說,在完成這一目標的過程當中,咱們不只僅須要基礎的代碼而無論代碼是否優雅。咱們還要對此進行抽象,來尋找一種適合構建更爲複雜的Node.js應用的方式。
咱們來分解一下這個應用,爲了實現上文的用例,咱們須要實現哪些部分呢?
- 咱們須要提供Web頁面,所以須要一個HTTP服務器
- 對於不一樣的請求,根據請求的URL,咱們的服務器須要給予不一樣的響應,所以咱們須要一個路由,用於把請求對應到請求處理程序(request handler)
- 當請求被服務器接收並經過路由傳遞以後,須要能夠對其進行處理,所以咱們須要最終的請求處理程序
- 路由還應該能處理POST數據,而且把數據封裝成更友好的格式傳遞給請求處理入程序,所以須要請求數據處理功能
- 咱們不只僅要處理URL對應的請求,還要把內容顯示出來,這意味着咱們須要一些視圖邏輯供請求處理程序使用,以便將內容發送給用戶的瀏覽器
- 最後,用戶須要上傳圖片,因此咱們須要上傳處理功能來處理這方面的細節 ######如今咱們就來開始實現之路,先從第一個部分--HTTP服務器着手。 ###4、構建應用的模塊 ####1.一個基礎的HTTP服務器 如今咱們來建立一個用於啓動咱們的應用的主文件,和一個保存着咱們的HTTP服務器代碼的模塊。
爲便於理解,把主文件叫作index.js或多或少是個標準格式。把服務器模塊放進叫server.js的文件裏則很好理解。
讓咱們先從服務器模塊開始。若是你clone了項目會發現咱們將要寫的server.js文件是不一樣的,從頭開始,在你的項目的根目錄下建立一個叫server.js的文件,通常狀況下,咱們會寫入如下代碼:
var http = require("http");
http.createServer(function(request, response){
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
複製代碼
這個時候,在終端中輸入
node server
,瀏覽器訪問http://localhost:8888,你會看到一個寫着「Hello World」的網頁。到這裏相信你們都很是很是的熟悉,可是關於這幾行代碼的原理與分析你是否考慮過,或者本身明白了?到這裏,咱們就先來談談HTTP服務器的問題,而把如何組織項目的事情先放一邊,我相信對於理解不深的人來講,會讓你對這幾行代碼有一個更清楚的理解。 ####2.分析HTTP服務器 第一行請求(require
)Node.js自帶的 http 模塊,而且把它賦值給 http 變量。
接下來咱們調用http模塊提供的函數: createServer 。這個函數會返回一個對象,這個對象有一個叫作 listen 的方法,這個方法有一個數值參數,指定這個HTTP服務器監聽的端口號。
我們暫時先無論 http.createServer 的括號裏的那個函數定義。
咱們原本能夠用這樣的代碼來啓動服務器並偵聽8888端口:
var server = http.createServer();
server.listen(8888);
複製代碼
至此你確定名了http是Node.js自帶的模塊,也知道了createServer是http模塊提供的函數,而且知道了這個函數返回的對象有一個listen偵聽端口的方法。接下來咱們要開始對createServer函數中的內容進行一個剖析。 ####3.進行函數傳遞 咱們先來看幾行代碼,並仔細閱讀它:
function say(word){
console.log(word);
}
function execute(someFunction,value){
someFunction(value);
}
execute(say,"Hello")
複製代碼
代碼分析(慢慢閱讀,不急不急):在這裏,咱們把 say 函數做爲execute函數的第一個變量進行了傳遞。這裏返回的不是 say 的返回值,而是 say 自己! 這樣一來, say 就變成了execute 中的本地變量 someFunction ,execute能夠經過調用 someFunction() (帶括號的形式)來使用 say 函數。 固然,由於 say 有一個變量, execute 在調用 someFunction 時能夠傳遞這樣一個變量。
咱們能夠,就像剛纔那樣,用它的名字把一個函數做爲變量傳遞。可是咱們不必定要繞這個「先定義,再傳遞」的圈子,咱們能夠直接在另外一個函數的括號中定義和傳遞這個函數:
function execute(someFunction,value){
someFunction(value)
}
execute(function(word){console.log(word)},"Hello")
複製代碼
代碼的囉嗦分析:
- 咱們在 execute 接受第一個參數的地方直接定義了咱們準備傳遞給 execute 的函數。
- 用這種方式,咱們甚至不用給這個函數起名字,這也是爲何它被叫作 匿名函數 。
- 咱們如今能夠接受這一點:在JavaScript中,一個 函數能夠做爲另外一個函數接收一個參數。咱們能夠先定義一個函數,而後傳遞,也能夠在傳遞參數的地方直接定義函數。
本章總共11節,本章第三節結束。 ####4.函數傳遞是如何讓HTTP服務器工做的
帶着剛纔咱們剛學習的一點小東西(若是有丁點疑惑,返回去繼續瞅一瞅,確保徹底明白每一個字節的意思),咱們來看看咱們簡約而不簡單的HTPP服務器:
var http = require("http");
http.createServer(function(request, response){
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);</pre>
複製代碼
是的,這個時候你已經知道,咱們向createServer函數傳遞了一個匿名函數。咱們能夠將上面代碼進行改觀與前面的知識進行碰撞(一樣的目的與做用):
var http = require("http");
function onRequest(request, response){
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
複製代碼
這個時候你有沒有疑問?咱們爲何要用這樣的方式呢?對於剛剛開始學習看到這裏的我來講,我確定不會去想這個問題的,開玩笑,我甚至都沒去想http這個東西是node自帶的模塊。
那麼做者提出的這個爲何,究竟是想要說什麼?咱們繼續進行下一節--【基於事件驅動的回調】,什麼是【基於事件驅動的回調】?這是什麼東西? 這個【基於事件驅動的回調】是個什麼東西?重要的文字寫三遍。
本章總共十一節,下面咱們開始第五節的內容。 ####5.基於事件驅動的回調 因而我帶着【基於事件驅動的回調】這個名詞來到本節的時候,做者首先說了一句「這個問題很差回答(至少對於我來講)」
而後關於這個事件驅動回調的背景知識,做者給了一篇文章的連接,感興趣的能夠去瞅瞅:debuggable.com/posts/under…
這一切都歸結於「Node.js是事件驅動的」這一事實。好吧,其實我也不是特別確切的瞭解這句話的意思。不過我會試着解釋,爲何它對咱們用Node.js寫網絡應用(Web based application)是有意義的。
- 當咱們使用 http.createServer 方法的時候,咱們固然不僅是想要一個偵聽某個端口的服務器,咱們還想要它在服務器收到一個HTTP請求的時候作點什麼。
問題是,這是異步的:請求任什麼時候候均可能到達,可是咱們的服務器卻跑在一個單進程中. 在咱們的Node.js程序中,當一個新的請求到達8888端口的時候,咱們怎麼控制流程呢?
- 咱們建立了服務器,而且向建立它的方法傳遞了一個函數。不管什麼時候咱們的服務器收到一個請求,這個函數就會被調用。
- 咱們不知道這件事情何時會發生,可是咱們如今有了一個處理請求的地方:它就是咱們傳遞過去的那個函數。至於它是被預先定義的函數仍是匿名函數,就可有可無了
- 這個就是傳說中的 回調 。咱們給某個方法傳遞了一個函數,這個方法在有相應事件發生時調用這個函數來進行 回調 。
讓咱們再來琢磨琢磨這個新概念。咱們怎麼證實,在建立完服務器以後,即便沒有HTTP請求進來、咱們的回調函數也沒有被調用的狀況下,咱們的代碼還繼續有效呢?咱們試試這個:
var http = require("http");
function onRequest(request, response){
console.log("Request received.");
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
複製代碼
代碼與解析分析: 當咱們與往常同樣,運行它
node server.js
時,它會立刻在命令行上輸出「Server has started.」。當咱們向服務器發出請求(在瀏覽器訪問http://localhost:8888/ ),「Request received.」這條消息就會在命令行中出現。
這就是事件驅動的異步服務器端JavaScript和它的回調啦!
(請注意,當咱們在服務器訪問網頁時,咱們的服務器可能會輸出兩次「Request received.」。那是由於大部分服務器都會在你訪問 http://localhost:8888 /時嘗試讀取 http://localhost:8888/favicon.ico )
######本節總結:說了一大堆,經過代碼咱們能夠簡單理解,服務器啓動執行後端console
方法,客戶端向服務器發出請求,再執行另外一個console
方法,這就是事件驅動的異步服務端JavaScript和它的回調。關於這個問題可自行多多研究。 本章總共11節,本章第五節結束。 ####6.服務器是如何處理請求的
好的,接下來咱們簡單分析一下咱們服務器代碼中剩下的部分,也就是咱們的回調函數 onRequest() 的主體部分。
當回調啓動,咱們的 onRequest() 函數被觸發的時候,有兩個參數被傳入: request 和 response 。
它們是對象,你可使用它們的方法來處理HTTP請求的細節,而且響應請求(好比向發出請求的瀏覽器發回一些東西)。
因此咱們的代碼就是:當收到請求時,使用 response.writeHead() 函數發送一個HTTP狀態200和HTTP頭的內容類型(content-type),使用 response.write() 函數在HTTP相應主體中發送文本「Hello World"。
最後,咱們調用 response.end() 完成響應。
目前來講,咱們對請求的細節並不在乎,因此咱們沒有使用 request 對象。 ###7.服務端的模塊放在哪裏 到這裏咱們開始說
如何組織應用
這個問題上。 把服務器腳本放到一個叫作start的函數裏,而後導出這個函數,在主文件中使用:
#server.js
var http = require("http");
function start(){
function onRequest(request, response){
console.log("Request received.");
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
exports.start = start;
#index.js
var server = require("./server");
server.start();
複製代碼
咱們仍然只擁有整個應用的最初部分:咱們能夠接收HTTP請求。可是咱們得作點什麼——對於不一樣的URL請求,服務器應該有不一樣的反應。對於一個很是簡單的應用來講,你能夠直接在回調函數 onRequest() 中作這件事情,可是,固然應用不會這麼簡單了。 處理不一樣的HTTP請求在咱們的代碼中是一個不一樣的部分,叫作「路由選擇」——那麼,咱們接下來就創造一個叫作 路由 的模塊吧。
本章總共11節,本章第⑦節結束。 ####8.如何來進行請求的「路由」
咱們要爲路由提供請求的URL和其餘須要的GET及POST參數,隨後路由須要根據這些數據來執行相應的代碼.
所以,咱們須要查看HTTP請求,從中提取出請求的URL以及GET/POST參數。
咱們須要的全部數據都會包含在request對象中,該對象做爲onRequest()回調函數的第一個參數傳遞。可是爲了解析這些數據,咱們須要額外的Node.JS模塊,它們分別是
ur
l和querystring
模塊。
如今咱們來給onRequest()函數加上一些邏輯,用來找出瀏覽器請求的URL路徑:
var http = require("http");
var url = require("url");
function start(){
function onRequest(request, response){
var pathname = url.parse(request.url).pathname;
console.log("Request for "+ pathname +" received.");
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
exports.start = start;
複製代碼
將上面代碼進行運行後,輸入不一樣的url能夠看到會輸出不一樣的
console
,咱們能夠經過請求的URL不一樣來區別不一樣的請求了。
如今咱們能夠來編寫路由了,創建一個名爲
router.js
的文件,添加如下內容:
function route(pathname){
console.log("About to route a request for "+ pathname);
}
exports.route = route;
複製代碼
這個時候,
index.js
、server.js
、rounter.js
全都有了,如何互相關聯就不贅述了。直接進入下一節。 ###9.行爲驅動執行 談一談函數編程: 將函數做爲參數傳遞並不只僅出於技術上的考量。對軟件設計來講,這實際上是個哲學問題。想一想這樣的場景:在index文件中,咱們能夠將router對象傳遞進去,服務器隨後能夠調用這個對象的route函數。
就像這樣,咱們傳遞一個東西,而後服務器利用這個東西來完成一些事。嗨那個叫路由的東西,能幫我把這個路由一下嗎?
可是服務器其實不須要這樣的東西。它只須要把事情作完就行,其實爲了把事情作完,你根本不須要東西,你須要的是動做。也就是說,你不須要名詞,你須要動詞。
理解了這個概念裏最核心、最基本的思想轉換後,我天然而然地理解了函數編程。
聽說這篇文章會讓你對函數編程有一個更好的理解:steve-yegge.blogspot.com/2006/03/exe…