Node.js 是什么
簡單的說 Node.js 就是運行于服務(wù)端的JavaScript解釋器,既然是解釋器糕伐,有很多地方也把Node.js 叫成運行環(huán)境雕崩。為什么強調(diào)服務(wù)端呢?其實是相對于瀏覽器客戶端來說的军俊,之前的javaScript就是負責(zé)瀏覽器客戶端程序的開發(fā),而Node.js打破了這個禁制捧存,使用javaScript可以快速構(gòu)建網(wǎng)絡(luò)服務(wù)及應(yīng)用粪躬,如向客戶端提供接口担败。這對于熟悉和重點使用javaScript的程序員和公司來說是意義重大的,搭建一些服務(wù)的成本是能夠降低的镰官。比如因此出現(xiàn)的成為“全棧工程師”的一條新路提前,就不在這里討論了。
解釋器的一些知識可以參考:java的編譯器和解釋器淺析
Chrome V8 引擎
如果大家去查Node.js的資料泳唠,經(jīng)常會出現(xiàn)這個詞:V8引擎狈网。原話基本上都是這樣的 —— “Node.js 是一個基于 Chrome V8引擎的JavaScript運行環(huán)境”啃龋” 那什么是V8引擎呢拓哺?
Chrome V8引擎是一個JavaScript VM,在運行JavaScript之前脖母,相比其它的JavaScript的引擎轉(zhuǎn)換成字節(jié)碼或解釋執(zhí)行士鸥,V8采用即時編譯技術(shù)(JIT),直接將JavaScript代碼編譯成本地平臺的機器碼谆级,然后直接交由硬件執(zhí)行烤礁,并且使用了如內(nèi)聯(lián)緩存(inline caching)等方法來提高性能。
有了這些功能肥照,JavaScript程序在V8引擎下的運行速度媲美二進制程序脚仔。
Node.js 和Chrome V8 引擎的關(guān)系
從解釋器這個角度講,我認為Node.js == V8引擎舆绎;但整體的看鲤脏,實際上它是對V8進行了封裝及優(yōu)化。Node對一些特殊用例進行了優(yōu)化吕朵,提供了替代的API凑兰,使得V8在非瀏覽器環(huán)境下運行得更好。例如边锁,在服務(wù)器環(huán)境中姑食,處理二進制數(shù)據(jù)通常是必不可少的,但Javascript對此支持不足茅坛,因此音半,Node增加了Buffer類,方便并且高效地 處理二進制數(shù)據(jù)贡蓖。因此曹鸠,Node不僅僅簡單的使用了V8,還對其進行了優(yōu)化斥铺,使其在各環(huán)境下更加給力彻桃。
Node.js架構(gòu)
Node.js 使用了一個事件驅(qū)動、非阻塞式I/O的模型晾蜘。在這里我要強調(diào)邻眷,語言和框架固然重要眠屎,然是其中的設(shè)計思想才是我們要重點記住的,因為互通的就是這些思想肆饶,并不是代碼本身改衩。
Node.js 標(biāo)準(zhǔn)庫,這部分是由 Javascript編寫的驯镊,即我們使用過程中直接能調(diào)用的 API葫督。
Node bindings,這一層是 Javascript與底層 C/C++ 能夠溝通的關(guān)鍵板惑,前者通過 bindings 調(diào)用后者橄镜,相互交換數(shù)據(jù)。
最下面這一層是支撐 Node.js 運行的關(guān)鍵冯乘,由 C/C++ 實現(xiàn)洽胶。
- V8: Google 推出的 Javascript VM,也是 Node.js 為什么使用的是 Javascript的關(guān)鍵往湿,它為 Javascript提供了在非瀏覽器端運行的環(huán)境妖异,它的高效是 Node.js 之所以高效的原因之一惋戏。
- Libuv: 它為 Node.js 提供了跨平臺领追,線程池,事件池响逢,異步 I/O 等能力绒窑,是 Node.js 如此強大的關(guān)鍵。
- C-ares: 提供了異步處理 DNS 相關(guān)的能力舔亭。
- http_parser些膨、OpenSSL、zlib 等: 提供包括 http 解析钦铺、SSL订雾、數(shù)據(jù)壓縮等其他的能力。
想要深入了解Node.js洼哎,幾個詞要記住并了解
- RESTful API
- 單線程
Node.js 并沒有給 Javascript 執(zhí)行時創(chuàng)建新線程的能力,它的執(zhí)行線程是單線程的沼本。但這并不會影響它對任務(wù)進行并發(fā)處理噩峦,通過非阻塞操作和事件驅(qū)動就可以實現(xiàn)并發(fā)。 -
非阻塞IO & 事件驅(qū)動
事件驅(qū)動
- 每個Node.js進程只有一個主線程在執(zhí)行程序代碼抽兆,形成一個執(zhí)行棧(execution context stack)识补。
- 主線程之外,還維護了一個"事件隊列"(Event queue)辫红。當(dāng)用戶的網(wǎng)絡(luò)請求或者其它的異步操作到來時凭涂,node都會把它放到Event Queue之中祝辣,此時并不會立即執(zhí)行它,代碼也不會被阻塞导盅,繼續(xù)往下走较幌,直到主線程代碼執(zhí)行完畢。
- 主線程代碼執(zhí)行完畢完成后白翻,然后通過Event Loop乍炉,也就是事件循環(huán)機制,開始到Event Queue的開頭取出第一個事件滤馍,從線程池中分配一個線程去執(zhí)行這個事件岛琼,接下來繼續(xù)取出第二個事件,再從線程池中分配一個線程去執(zhí)行巢株,然后第三個槐瑞,第四個。主線程不斷的檢查事件隊列中是否有未執(zhí)行的事件阁苞,直到事件隊列中所有事件都執(zhí)行完了困檩,此后每當(dāng)有新的事件加入到事件隊列中,都會通知主線程按順序取出交EventLoop處理那槽。當(dāng)有事件執(zhí)行完畢后悼沿,會通知主線程,主線程執(zhí)行回調(diào)骚灸,線程歸還給線程池糟趾。
- 主線程不斷重復(fù)上面的第三步。
總結(jié):
我們所看到的node.js單線程只是一個js主線程甚牲,本質(zhì)上的異步操作還是由線程池完成的义郑,node將所有的阻塞操作都交給了內(nèi)部的線程池去實現(xiàn),本身只負責(zé)不斷的往返調(diào)度丈钙,并沒有進行真正的I/O操作非驮,從而實現(xiàn)異步非阻塞I/O,這便是node單線程和事件驅(qū)動的精髓之處了雏赦。
一個服務(wù)的搭建實例
將以下代碼存儲為js文件并運行劫笙,在瀏覽器中打開 http://127.0.0.1:8888/ ,你的第一個node應(yīng)用就能使用了喉誊。
var http = require('http');
http.createServer(function (request, response) {
// 發(fā)送 HTTP 頭部
// HTTP 狀態(tài)值: 200 : OK
// 內(nèi)容類型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});
// 發(fā)送響應(yīng)數(shù)據(jù) "Hello World"
response.end('Hello World\n');
}).listen(8888);
// 終端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');
最后我們看2個有意思的程序邀摆,大家結(jié)合學(xué)到的知識,猜猜打印的結(jié)果可能是什么樣子的
第一個:
for (var i = 0; i <= 5; i++) {
setTimeout(function() {
console.log( i );
}, i*1000);
console.log( ' i : ' , i );
}
console.log( ' i i: ' , i );
第二個:
// 獲取當(dāng)前時間戳
var start = Date.now();
// 2秒后獲取時間間隔
setTimeout(function () {
console.log(Date.now() - start);
}, 2000);
// 1秒后獲取時間間隔并開始運行長循環(huán)
setTimeout(function () {
console.log(Date.now() - start);
for (var i = 0; i < 1000000000; i++) {
// 執(zhí)行長循環(huán)
}
}, 1000);
//
var fs = require("fs");
fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序執(zhí)行結(jié)束!");
感謝以下借鑒的大牛文章
https://www.runoob.com/nodejs/nodejs-http-server.html
https://blog.csdn.net/xiahuale/article/details/87005901
https://baike.baidu.com/item/node.js/7567977?fromtitle=nodejs&fromid=11244313&fr=aladdin