JavaScript與Node.js
JavaScript與你
拋開技術(shù)偷遗,我們先來聊聊你以及你和JavaScript的關(guān)系醉拓。本章的主要目的是想讓你看看伟姐,對你而言是否有必要繼續(xù)閱讀后續(xù)章節(jié)的內(nèi)容收苏。
如果你和我一樣,那么你很早就開始利用HTML進(jìn)行“開發(fā)”愤兵,正因如此鹿霸,你接觸到了這個叫JavaScript有趣的東西,而對于JavaScript秆乳,你只會基本的操作——為web頁面添加交互懦鼠。
而你真正想要的是“干貨”,你想要知道如何構(gòu)建復(fù)雜的web站點(diǎn) —— 于是屹堰,你學(xué)習(xí)了一種諸如PHP肛冶、Ruby、Java這樣的編程語言扯键,并開始書寫“后端”代碼睦袖。
與此同時,你還始終關(guān)注著JavaScript忧陪,隨著通過一些對jQuery扣泊,Prototype之類技術(shù)的介紹,你慢慢了解到了很多JavaScript中的進(jìn)階技能嘶摊,同時也感受到了JavaScript絕非僅僅是window.open()那么簡單延蟹。 .
不過,這些畢竟都是前端技術(shù)叶堆,盡管當(dāng)想要增強(qiáng)頁面的時候阱飘,使用jQuery總讓你覺得很爽,但到最后虱颗,你頂多是個JavaScript用戶沥匈,而非JavaScript開發(fā)者。
然后忘渔,出現(xiàn)了Node.js高帖,服務(wù)端的JavaScript,這有多酷捌枇浮散址?
于是,你覺得是時候該重新拾起既熟悉又陌生的JavaScript了宣赔。但是別急预麸,寫Node.js應(yīng)用是一件事情;理解為什么它們要以它們書寫的這種方式來書寫則意味著——你要懂JavaScript儒将。這次是玩真的了吏祸。
問題來了: 由于JavaScript真正意義上以兩種,甚至可以說是三種形態(tài)存在(從中世紀(jì)90年代的作為對DHTML進(jìn)行增強(qiáng)的小玩具钩蚊,到像jQuery那樣嚴(yán)格 意義上的前端技術(shù)贡翘,一直到現(xiàn)在的服務(wù)端技術(shù))蹈矮,因此,很難找到一個“正確”的方式來學(xué)習(xí)JavaScript床估,使得讓你書寫Node.js應(yīng)用的時候感覺 自己是在真正開發(fā)它而不僅僅是使用它含滴。
因為這就是關(guān)鍵: 你本身已經(jīng)是個有經(jīng)驗的開發(fā)者诱渤,你不想通過到處尋找各種解決方案(其中可能還有不正確的)來學(xué)習(xí)新的技術(shù)丐巫,你要確保自己是通過正確的方式來學(xué)習(xí)這項技術(shù)。
當(dāng)然了勺美,外面不乏很優(yōu)秀的學(xué)習(xí)JavaScript的文章递胧。但是,有的時候光靠那些文章是遠(yuǎn)遠(yuǎn)不夠的赡茸。你需要的是指導(dǎo)缎脾。
本書的目標(biāo)就是給你提供指導(dǎo)。更多學(xué)習(xí)資料請點(diǎn)擊此處
簡短申明
業(yè)界有非常優(yōu)秀的JavaScript程序員占卧。而我并非其中一員遗菠。
我就是上一節(jié)中描述的那個我。我熟悉如何開發(fā)后端web應(yīng)用华蜒,但是對“真正”的JavaScript以及Node.js辙纬,我都只是新手。我也只是最近學(xué)習(xí)了一些JavaScript的高級概念叭喜,并沒有實(shí)踐經(jīng)驗贺拣。
因此,本書并不是一本“從入門到精通”的書捂蕴,更像是一本“從初級入門到高級入門”的書譬涡。
如果成功的話,那么本書就是我當(dāng)初開始學(xué)習(xí)Node.js最希望擁有的教程啥辨。
服務(wù)端JavaScript
JavaScript最早是運(yùn)行在瀏覽器中涡匀,然而瀏覽器只是提供了一個上下文,它定義了使用JavaScript可以做什么溉知,但并沒有“說”太多關(guān)于 JavaScript語言本身可以做什么陨瘩。事實(shí)上,JavaScript是一門“完整”的語言: 它可以使用在不同的上下文中着倾,其能力與其他同類語言相比有過之而無不及拾酝。
Node.js事實(shí)上就是另外一種上下文,它允許在后端(脫離瀏覽器環(huán)境)運(yùn)行JavaScript代碼卡者。
要實(shí)現(xiàn)在后臺運(yùn)行JavaScript代碼蒿囤,代碼需要先被解釋然后正確的執(zhí)行。Node.js的原理正是如此崇决,它使用了Google的V8虛擬機(jī) (Google的Chrome瀏覽器使用的JavaScript執(zhí)行環(huán)境)材诽,來解釋和執(zhí)行JavaScript代碼底挫。
除此之外,伴隨著Node.js的還有許多有用的模塊脸侥,它們可以簡化很多重復(fù)的勞作建邓,比如向終端輸出字符串。
因此睁枕,Node.js事實(shí)上既是一個運(yùn)行時環(huán)境官边,同時又是一個庫。
要使用Node.js,首先需要進(jìn)行安裝外遇。關(guān)于如何安裝Node.js注簿,這里就不贅述了,可以直接參考官方的安裝指南跳仿。安裝完成后诡渴,繼續(xù)回來閱讀本書下面的內(nèi)容。
“Hello World”
好了菲语,“廢話”不多說了妄辩,馬上開始我們第一個Node.js應(yīng)用:“Hello World”。
打開你最喜歡的編輯器山上,創(chuàng)建一個helloworld.js文件眼耀。我們要做就是向STDOUT輸出“Hello World”,如下是實(shí)現(xiàn)該功能的代碼:
console.log("Hello World");
保存該文件胶哲,并通過Node.js來執(zhí)行:
node helloworld.js
正常的話畔塔,就會在終端輸出Hello World。
好吧鸯屿,我承認(rèn)這個應(yīng)用是有點(diǎn)無趣澈吨,那么下面我們就來點(diǎn)“干貨”。
一個完整的基于Node.js的web應(yīng)用
用例
我們來把目標(biāo)設(shè)定得簡單點(diǎn)寄摆,不過也要夠?qū)嶋H才行:
用戶可以通過瀏覽器使用我們的應(yīng)用谅辣。
當(dāng)用戶請求http://domain/start時,可以看到一個歡迎頁面婶恼,頁面上有一個文件上傳的表單桑阶。
用戶可以選擇一個圖片并提交表單,隨后文件將被上傳到http://domain/upload勾邦,該頁面完成上傳后會把圖片顯示在頁面上蚣录。
差不多了,你現(xiàn)在也可以去Google一下眷篇,找點(diǎn)東西亂搞一下來完成功能萎河。但是我們現(xiàn)在先不做這個。
更進(jìn)一步地說,在完成這一目標(biāo)的過程中虐杯,我們不僅僅需要基礎(chǔ)的代碼而不管代碼是否優(yōu)雅玛歌。我們還要對此進(jìn)行抽象,來尋找一種適合構(gòu)建更為復(fù)雜的Node.js應(yīng)用的方式擎椰。
應(yīng)用不同模塊分析
我們來分解一下這個應(yīng)用支子,為了實(shí)現(xiàn)上文的用例,我們需要實(shí)現(xiàn)哪些部分呢达舒?
我們需要提供Web頁面值朋,因此需要一個HTTP服務(wù)器
對于不同的請求,根據(jù)請求的URL休弃,我們的服務(wù)器需要給予不同的響應(yīng)吞歼,因此我們需要一個路由,用于把請求對應(yīng)到請求處理程序(request handler)
當(dāng)請求被服務(wù)器接收并通過路由傳遞之后塔猾,需要可以對其進(jìn)行處理,因此我們需要最終的請求處理程序
路由還應(yīng)該能處理POST數(shù)據(jù)稽坤,并且把數(shù)據(jù)封裝成更友好的格式傳遞給請求處理入程序丈甸,因此需要請求數(shù)據(jù)處理功能
我們不僅僅要處理URL對應(yīng)的請求,還要把內(nèi)容顯示出來尿褪,這意味著我們需要一些視圖邏輯供請求處理程序使用睦擂,以便將內(nèi)容發(fā)送給用戶的瀏覽器
最后,用戶需要上傳圖片杖玲,所以我們需要上傳處理功能來處理這方面的細(xì)節(jié)
我們先來想想顿仇,使用PHP的話我們會怎么構(gòu)建這個結(jié)構(gòu)。一般來說我們會用一個Apache HTTP服務(wù)器并配上mod_php5模塊摆马。
從這個角度看臼闻,整個“接收HTTP請求并提供Web頁面”的需求根本不需要PHP來處理。
不過對Node.js來說囤采,概念完全不一樣了述呐。使用Node.js時,我們不僅僅在實(shí)現(xiàn)一個應(yīng)用蕉毯,同時還實(shí)現(xiàn)了整個HTTP服務(wù)器乓搬。事實(shí)上,我們的Web應(yīng)用以及對應(yīng)的Web服務(wù)器基本上是一樣的代虾。
聽起來好像有一大堆活要做进肯,但隨后我們會逐漸意識到,對Node.js來說這并不是什么麻煩的事棉磨。
現(xiàn)在我們就來開始實(shí)現(xiàn)之路江掩,先從第一個部分--HTTP服務(wù)器著手。
構(gòu)建應(yīng)用的模塊
一個基礎(chǔ)的HTTP服務(wù)器
當(dāng)我準(zhǔn)備開始寫我的第一個“真正的”Node.js應(yīng)用的時候,我不但不知道怎么寫Node.js代碼频敛,也不知道怎么組織這些代碼项郊。
我應(yīng)該把所有東西都放進(jìn)一個文件里嗎?網(wǎng)上有很多教程都會教你把所有的邏輯都放進(jìn)一個用Node.js寫的基礎(chǔ)HTTP服務(wù)器里斟赚。但是如果我想加入更多的內(nèi)容着降,同時還想保持代碼的可讀性呢?
實(shí)際上拗军,只要把不同功能的代碼放入不同的模塊中任洞,保持代碼分離還是相當(dāng)簡單的。
這種方法允許你擁有一個干凈的主文件(main file)发侵,你可以用Node.js執(zhí)行它交掏;同時你可以擁有干凈的模塊,它們可以被主文件和其他的模塊調(diào)用刃鳄。
那么盅弛,現(xiàn)在我們來創(chuàng)建一個用于啟動我們的應(yīng)用的主文件,和一個保存著我們的HTTP服務(wù)器代碼的模塊叔锐。
在我的印象里挪鹏,把主文件叫做index.js或多或少是個標(biāo)準(zhǔn)格式。把服務(wù)器模塊放進(jìn)叫server.js的文件里則很好理解愉烙。
讓我們先從服務(wù)器模塊開始讨盒。在你的項目的根目錄下創(chuàng)建一個叫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);
搞定步责!你剛剛完成了一個可以工作的HTTP服務(wù)器返顺。為了證明這一點(diǎn),我們來運(yùn)行并且測試這段代碼蔓肯。首先遂鹊,用Node.js執(zhí)行你的腳本:
node server.js
接下來,打開瀏覽器訪問http://localhost:8888/省核,你會看到一個寫著“Hello World”的網(wǎng)頁稿辙。
這很有趣,不是嗎气忠?讓我們先來談?wù)凥TTP服務(wù)器的問題邻储,把如何組織項目的事情先放一邊吧,你覺得如何旧噪?我保證之后我們會解決那個問題的吨娜。
分析HTTP服務(wù)器
那么接下來,讓我們分析一下這個HTTP服務(wù)器的構(gòu)成淘钟。
第一行請求(require)Node.js自帶的http模塊宦赠,并且把它賦值給http變量。
接下來我們調(diào)用http模塊提供的函數(shù):createServer。這個函數(shù)會返回一個對象勾扭,這個對象有一個叫做listen的方法毡琉,這個方法有一個數(shù)值參數(shù),指定這個HTTP服務(wù)器監(jiān)聽的端口號妙色。
咱們暫時先不管http.createServer的括號里的那個函數(shù)定義桅滋。
我們本來可以用這樣的代碼來啟動服務(wù)器并偵聽8888端口:
var http = require("http");
var server = http.createServer();
server.listen(8888);
這段代碼只會啟動一個偵聽8888端口的服務(wù)器,它不做任何別的事情身辨,甚至連請求都不會應(yīng)答丐谋。
最有趣(而且,如果你之前習(xí)慣使用一個更加保守的語言煌珊,比如PHP号俐,它還很奇怪)的部分是createSever()的第一個參數(shù),一個函數(shù)定義定庵。
實(shí)際上吏饿,這個函數(shù)定義是createServer()的第一個也是唯一一個參數(shù)。因為在JavaScript中洗贰,函數(shù)和其他變量一樣都是可以被傳遞的找岖。
進(jìn)行函數(shù)傳遞
舉例來說,你可以這樣做:
function say(word){
console.log(word);
}
function execute(someFunction, value){
someFunction(value);
}
execute(say,"Hello");
請仔細(xì)閱讀這段代碼敛滋!在這里,我們把say函數(shù)作為execute函數(shù)的第一個變量進(jìn)行了傳遞兴革。這里返回的不是say的返回值绎晃,而是say本身!
這樣一來杂曲,say就變成了execute中的本地變量someFunction庶艾,execute可以通過調(diào)用someFunction()(帶括號的形式)來使用say函數(shù)。
當(dāng)然擎勘,因為say有一個變量咱揍,execute在調(diào)用someFunction時可以傳遞這樣一個變量。
我們可以棚饵,就像剛才那樣煤裙,用它的名字把一個函數(shù)作為變量傳遞。但是我們不一定要繞這個“先定義噪漾,再傳遞”的圈子硼砰,我們可以直接在另一個函數(shù)的括號中定義和傳遞這個函數(shù):
function execute(someFunction, value){
someFunction(value);
}
execute(function(word){ console.log(word)},"Hello");
我們在execute接受第一個參數(shù)的地方直接定義了我們準(zhǔn)備傳遞給execute的函數(shù)。
用這種方式欣硼,我們甚至不用給這個函數(shù)起名字题翰,這也是為什么它被叫做匿名函數(shù)。
這是我們和我所認(rèn)為的“進(jìn)階”JavaScript的第一次親密接觸,不過我們還是得循序漸進(jìn)”希現(xiàn)在冯事,我們先接受這一點(diǎn):在JavaScript中,一個 函數(shù)可以作為另一個函數(shù)接收一個參數(shù)血公。我們可以先定義一個函數(shù)昵仅,然后傳遞,也可以在傳遞參數(shù)的地方直接定義函數(shù)坞笙。
函數(shù)傳遞是如何讓HTTP服務(wù)器工作的
帶著這些知識岩饼,我們再來看看我們簡約而不簡單的HTTP服務(wù)器:
var http = require("http");
http.createServer(function(request, response){
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
現(xiàn)在它看上去應(yīng)該清晰了很多:我們向createServer函數(shù)傳遞了一個匿名函數(shù)。
用這樣的代碼也可以達(dá)到同樣的目的:
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);
也許現(xiàn)在我們該問這個問題了:我們?yōu)槭裁匆眠@種方式呢薛夜?
基于事件驅(qū)動的回調(diào)?實(shí)在太多籍茧,有興趣的朋友可點(diǎn)擊閱讀全文