一、NodeJS簡介
NodeJS是開發伺服器後臺的東西,和PHP、JavaEE、python類似,和傳統的瀏覽器的關注DOM的JS完全不同,將JavaScript觸角伸到了伺服器端。核心是Chrome瀏覽器的V8引擎,解析JavaScript的效率是非常快的。
創始人。
在不升級伺服器配置的情況下,如何用軟體手段來提升伺服器效能:Ryan Dahl大致的感覺到了解決問題的關鍵是要通過事件驅動和非同步I/O來達成目的。
傳統的伺服器模型:當我們做I/O操作的時候(I表示讀,O表示寫),CPU被磁碟操作阻塞了,此時我們稱這叫做“同步I/O(synchronous I/O),阻塞I/O(blocking I/O)”。
CPU會經常對磁碟驅動發出I/O命令
此時磁碟特別忙,CPU就歇著了。造成了資源浪費。上圖這種模式叫做“同步I/O”。
同步(synchronous):當系統遇見了一個需要耗費大量時間的事情的時候,選擇死等。
非同步(Asynchronous):當系統遇見了一個需要耗費大量時間的事情的時候,不死等,先做後面的事情,耗時事情做完之後,執行回撥函式。
用PHP做一個例子:下面程式中紅色部分是I/O操作,此時CPU被阻塞,此時為什麼不限做藍色計算部分?等紅色部分做完了用“回撥函式”來顯示檔案內容多好。
<?php //讀取檔案 $myfile = fopen("txt.txt", "r"); //列印檔案記憶體 echo fread($myfile, filesize("txt.txt")); for($i = 2; $i < 100; $i++){ $count = 0; for($j = 1; $j <= $i; $j++){ if($i % $j == 0){ $count++; } } if($count == 2){ echo $i."<br/>"; } } fclose($myfile); ?>
V8引擎來了。V8滿足他關於高效能Web伺服器的想象:
● 沒有歷史包袱,沒有同步I/O。不會出現一個同步I/O導致事件迴圈效能急劇降低的情況。
● V8效能足夠好,遠遠比Python、Ruby等其他指令碼語言的引擎快。
● JavaScript語言的閉包特性非常方便,比C中的回撥函式好用。
創始人Ryan Dahl想到了用V8引擎核心,用JS當做語言去開發服務端程式。
下面是Nodejs的畫風,Nodejs不是語言,語言是JavaScript,Nodejs是一個平臺,讓我們的JS可以執行在服務端的平臺。
var fs = require("fs"); fs.readFile("./txt.txt" ,function(err, data){ console.log(data.toString()); }) for(var i = 2; i < 100; i++){ count = 0; for(var j = 1; j <= i;j++){ if(i % j == 0){ count++; } } if(count == 2){ console.log(i) } }
紅色的I/O操作沒有將藍色計算操作阻塞,稱為“Non-Blocking I/O”非阻塞I/O。紫色語句是回撥函式。
NodeJS是一個JS執行環境,它構建在chrome的V8引擎上。使用了事件驅動、非阻塞I/O模型,使它輕量並且方便。NodeJS有一個全球最大的包生態系統npm。
Node.js使JavaScript的觸角伸到了伺服器開發中,在Node的世界中,我們關心的不再是用JS操作網頁上的DOM、製作互動動畫、表單驗證……而是伺服器端的事情:HTTP請求的處理、GET請求和POST請求、資料庫增刪改查、cookie和session等等。
Node.js的特點:單執行緒、非阻塞非同步I/O、事件驅動。
Nodejs是JS的執行環境,它構建在Chrome的V8引擎上。
Nodejs是一個小極客,追求極致的伺服器效能,劍走偏鋒的產物。
與其很多服務員閒著,還不如一個服務員100%的工作。
二、NodeJS的安裝
NodeJS和Java虛擬機器一樣,是跨平臺的,也就是說只要寫一份程式碼,可以執行在任何平臺上。
去NodeJS官網下載:
|
安裝的歡迎介面 |
|
同意協議 |
|
安裝路徑全程不能有中文。 事實上很多軟體公司已經去中文化,文件都是英語的檔名,資料夾都是英語的。 |
|
這個介面不需要改變任何的配置,但是要知道:這裡安裝了4個東西。
|
|
點選下一步就開始安裝了 |
|
系統的防火牆,選擇是 |
|
nodejs已經被成功的安裝。 |
安裝完畢之後,按windows鍵加R鍵(run),輸入cmd(表示commond命令)按回車:
這是系統的密令提示符,可以輸入類似DOS的命令。但是現在要輸入
node -v
追求以下真理,安裝過程,到底發生了什麼?發現了一個Nodejs程式:
我們發現系統的環境變數中,已經新增了這個路徑:
系統的環境變數可以保證我們的node在任意CMD碟符下可以被執行。
nodejs在windows中是一個exe程式。
三、執行Nodejs程式
在c盤建立一個nodejs_study檔案,然後寫01.js檔案
將檔案拖拽到瀏覽器中不能執行的。
因為js的執行需要宿主環境(runtime),目前我們只知道一個環境:瀏覽器+HTML環境。
想要執行01.js程式:
<html> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body> </body> <script type="text/javascript" src="01.js"></script> </html>
瀏覽器會渲染html就能執行js。
NodeJS是一個全新的JS宿主環境runtime。也就是說可以用node執行js程式,而不需要html和瀏覽器。
開啟CMD,首先注意游標所在碟符位置:
01.js檔案不在C:\Users\admin>中,而是在C:\nodejs_study,所以要用cd命令來切換游標所在碟符位置:
當用cd切換到01.js所在目錄時,此時可以用node命令執行js檔案:
也就是說,執行js檔案的時候:
① 要保證游標所在位置正確
② 執行誰,就node誰。
為了快速開啟正確游標所在位置,此時有奇淫技巧:
要求大家,在執行node程式的時候,必須進入準確的碟符,不能使用絕對路徑:
最快的執行node程式的方法就是使用visual studio code,或者webstrom。直接按F5,IDE(程式設計工具)內建的控制檯會輸出結果。
CMD的命令:
cls
清屏。
cd ..
回退到上一級資料夾
cd 資料夾名字
進入這個資料夾
dir
列出當前目錄中所有的檔案
↑上箭頭
重複上一條指令。
四、NodeJS內建模組
4.1 fs模組
NodeJS沒有自己的語法,JS能寫什麼,node就能執行什麼
但是要注意,僅限於JS語言核心部分。DOM、BOM不能用,node沒有瀏覽器的那些東西。
document.getElementById("box");
window
alert()
但是可以用定時器
setInterval(function(){ console.log(Math.random()); },100)
按ctrl+c可以打斷Nodejs程式的執行。
Nodejs中提供了很多內建模組,幫助我們開發,可以提供瀏覽器所不具備的功能,模組是內建的,不需要額外安裝。從fs內建模組學起,fs是file system檔案系統的意思,提供了對檔案的所有操作
// require表示得到,引入fs內建模組,file system檔案系統模組 var fs = require("fs"); //fs模組有個方法叫readFile,可以非同步讀檔案內容 //這個函式是非同步函式,也就是說讀取檔案不會阻塞CPU執行,所以讀取到的內容通過回撥函式返回 //err引數:錯誤資訊,沒有錯誤將返回null //data引數:返回檔案的內容 fs.readFile("./test1.txt",function(err,data){ if(err){ console.log("檔案讀取錯誤!"); return; } //讀取的是資訊流,是Buffer是緩衝的二進位制,用toString()轉為字串 console.log(data.toString()); });
利用fs.readFile()這個API來讀取檔案,並且是非同步讀取檔案,這裡注意兩個事情:
1) 第一個引數是路徑,必須以"./"開頭,表示從相對於當前的cmd碟符位置去讀取檔案。
2) 第二個引數是回撥函式,注意回撥函式中提供了兩個引數,分別是err和data。注意,nodejs中的所有回撥函式的第一個引數都是err物件,表示錯誤。如果沒有錯誤,這個物件是null。
要會看API:https://nodejs.org/dist/latest-v6.x/docs/api/
var fs = require("fs"); fs.readFile("./test1.txt",function(err,data){ console.log(data.toString()); }); fs.readFile("./test2.txt",function(err,data){ console.log(data.toString()); }); fs.readFile("./test3.txt",function(err,data){ console.log(data.toString()); });
在Nodejs中要適應回撥套回撥函式的寫法:
var fs = require("fs"); fs.readFile("./test1.txt",function(err,data){ console.log(data.toString()); fs.readFile("./test2.txt",function(err,data){ console.log(data.toString()); fs.readFile("./test3.txt",function(err,data){ console.log(data.toString()); }); }); });
4.2 http模組
用http模組,Nodejs可以開發伺服器。建立伺服器的API要自己背誦記憶一下,不過,後面講解Express,極大簡化伺服器的建立:
var http = require('http'); //讀取內建http模組,這個模組提供了http服務 //建立一個伺服器,是非同步函式,事實上node中基本所有函式都是非同步的 var server = http.createServer(function(req,res){ //req表示request使用者的請求 //res表示response伺服器的響應 //設定響應報文頭,讓型別變為html並且是utf8編碼 res.setHeader("Content-type", "text/html;charset=UTF8"); //頁面上輸出內容,輸出內容用write,end表示結束! res.end("<h1>你好,我是nodejs開發的伺服器!</h1>"); }); //監聽3000埠,預設是80埠,但80被Apache佔用了,所以改用3000 server.listen(3000,function(err){ if(err){ console.log("伺服器開啟失敗!"); return; } console.log("伺服器開啟成功,在3000埠,快開啟瀏覽器看看吧!"); });
此時CMD會被掛起,按ctrl+c打斷(打斷之後,伺服器也打不開了)
我們來驗證程式確實在伺服器上執行的:
var http = require('http'); var server = http.createServer(function(req,res){ res.setHeader("Content-type", "text/html;charset=UTF8"); res.end("<h1>你好,我是nodejs開發的伺服器!已經誕生了"+ (3 + 3) +"年了</h1>"); }); server.listen(3000);
在瀏覽器中,看不到後端node的原始碼:
原因是Nodejs執行在伺服器端,語句在伺服器被執行、編譯,此時發給瀏覽器的就是執行之後、編譯後的純文字了,不含有計算的。
兩個問題:
1、我用Nodejs搭建的伺服器,訪問者也必須安裝nodejs嗎。
不需要的,因為nodejs是伺服器程式,生成的內容僅需要瀏覽器就能訪問
2、我們用高階ES6語法書寫Nodejs程式,必須用高階瀏覽器訪問嗎。
不需要的,因為Nodejs程式不在瀏覽器執行。
req表示request使用者的請求,訪問者的所有必要資訊在req中儲存,比如
req.url
表示訪問路徑
req.connection.remoteAddress
表示訪問者的ip地址
res表示response伺服器的響應,伺服器應該給與的反饋。 res.setHeader("Content-type", "text/html;charset=UTF8");
設定HTTP下行報文體
l res.end() 、res.write()負責寫下行報文體(就是網頁)。
res.end()結束響應,告訴客戶端所有訊息已經傳送完畢,當所有要返回的內容傳送完畢時,該函式必須被呼叫一次,如果不呼叫該函式,客戶端將永遠處於等待狀態。
var http = require('http'); var server = http.createServer((req,res)=>{ res.setHeader("Content-type", "text/html;charset=UTF8"); //頁面顯示的內容 res.write("<h1>hello world!</h1>"); res.write("<ul>"); res.write("<li>Nodejs</li>"); res.write("<li>npm</li>"); res.write("<li>Express</li>"); res.write("<li>Mongodb</li>"); res.write("</ul>"); res.end(); //必須結束,否則本次操作不結束,瀏覽器顯示為載入中 }); server.listen(3000);
五、NodeJS特點
5.1單執行緒
寫一段程式,證明NodeJS是單執行緒的:
var http = require("http"); //在伺服器外面定義一個變數a var a = 0; var server = http.createServer(function(req,res){ a++; res.end(a.toString()); }); server.listen(3000)
我們發現不是每個訪問者來了都有新的a值,而是全班共用一個a,每個人訪問的時候看到的都是已經被刷過的a值。
var http = require("http"); var server = http.createServer(function(req,res){ var a = ~~(Math.random() * 1000); if(a == 38){ throw new Error("有人踩地雷了,此人IP:" + req.connection.remoteAddress); } res.end("<h1>"+ a +"</h1>"); }); server.listen(3000)
在Java、PHP或者.net等伺服器端語言中,會為每一個客戶端連線建立一個新的程式。而每個程式需要耗費大約2MB記憶體。也就是說,理論上一個8GB記憶體的伺服器可以同時連線的最大使用者數為4000個左右。要讓Web應用程式支援更多的使用者,就需要增加伺服器的數量,而Web應用程式的硬體成本當然就上升了。
打個比喻,PHP假如是一個餐館,那麼就是一個這樣的餐館:來一個使用者,它就給你新招聘一個服務員,專門伺候你。你點菜的時候,這個服務員幫你點菜,你開始吃了,你這個服務員就閒置。這就造成了大量的浪費,憑什麼閒置服務員,為什麼不去服務別人?
Node.js不為每個客戶連線建立一個新的執行緒,而僅僅使用一個執行緒。當有使用者連線了,就觸發一個內部事件,通過非阻塞I/O、事件驅動機制,讓Node.js程式巨集觀上也是並行的。使用Node.js,一個8GB記憶體的伺服器,可以同時處理超過4萬使用者的連線。
打個比喻,Node.js假如是一個餐館,那麼就是一個這樣的餐館:總共就一個服務員,A使用者來了,幫A使用者點菜,B使用者來了,B使用者稍微被阻塞一下,A點完菜之後,廚師開始做,服務B,B開始點菜,點菜的時候A的菜做好了,給B點完菜之後再去給A端菜,A開始吃,此時C來了,說了句歡迎光臨之後,“您看看選單先”,就去給B端菜去了,B開始吃上了,此時C開始點菜……。
微觀上,A、B、C不是並行的,但是巨集觀上他們是並行的!
另外,單執行緒的帶來的好處,還有作業系統完全不再有執行緒建立、銷燬的時間開銷。
5.2非同步I/O特性
再來一個案例,使用者訪問頁面的時候,伺服器要讀取兩個檔案,此時用console檢視唯一的服務員如何工作:
var server = http.createServer((req,res) => { //使用者訪問的時候執行的語句。 console.log(req.connection.remoteAddress + "進門了!我招呼他去!"); res.setHeader("Content-Type","text/html;charset=UTF8"); console.log(req.connection.remoteAddress + "使用者開始讀1號檔案"); fs.readFile("./test1.txt",(err,data1) => { console.log(req.connection.remoteAddress + "使用者讀完畢1號檔案,開始讀2號檔案"); fs.readFile("./test2.txt",(err,data2) => { console.log(req.connection.remoteAddress+"使用者讀完2號檔案,開始傳送response"); res.write("<h1>" + data1.toString() +"</h1>"); res.write("<h1>" + data2.toString() +"</h1>"); res.end(""); }); }); });
你I/O的時候CPU並沒有被阻塞,去伺候別人,當別人也I/O了此時執行你的回撥函式。
綜上所述,nodejs是一個服務員在伺候所有人,所以擅長I/O越多的業務。I/O越多,效率越高,nodejs不會錯亂,採用就是事件環機制,也叫事件驅動機制,nodejs不擅長計算多的業務,因為給一個使用者計算去了,此時唯一的執行緒就被阻塞了,其他使用者進來就等待。
比如下面的程式,紅色的部分是計算水仙花數,計算很複雜,但是是同步的,所以小服務員就被阻塞了:
var server = http.createServer((req,res) => { //使用者訪問的時候執行的語句。 console.log(req.connection.remoteAddress + "進門了!我招呼他去!"); console.log(req.connection.remoteAddress + "開始計算水仙花數了!"); for(var i = 100 ; i <= 999 ; i++){ // 給三位數拆分成三個單獨的數 var ge = i % 10; var shi = parseInt(i / 10) % 10; var bai = parseInt(i / 100); // 計算三次方的和 var sum = Math.pow(ge,3) + Math.pow(shi,3) + Math.pow(bai,3); // 判斷輸出 if(sum == i){ console.log(req.connection.remoteAddress + "計算出了水仙花數" + i); } } res.setHeader("Content-Type","text/html;charset=UTF8"); console.log(req.connection.remoteAddress + "開始讀取檔案"); fs.readFile("./test1.txt",(err,data1) => { console.log(req.connection.remoteAddress + "讀取檔案完畢,開始輸出響應流"); res.write("<h1>" + data1.toString() +"</h1>"); res.end(""); }); });
5.3事件驅動
事件驅動是nodejs的底層機制,我們只需瞭解nodejs不會“上錯菜”,原因就是事件驅動,有一個事件環。
在Node中,客戶端請求建立連線,提交資料等行為,會觸發相應的事件。在Node中,在一個時刻,只能執行一個事件回撥函式,但是在執行一個事件回撥函式的中途,可以轉而處理其他事件(比如,又有新使用者連線了),然後返回繼續執行原事件的回撥函式,這種處理機制,稱為“事件環”機制。
Node.js底層是C++(V8也是C++寫的)。底層程式碼中,近半數都用於事件佇列、回撥函式佇列的構建。用事件驅動來完成伺服器的任務排程,這是鬼才才能想到的。
2013年開始,Nodejs突然間火爆,因為Nodejs的工作流工具火了!
我們現在學習nodejs的意義,已經從學習HTTP伺服器的構建,變為學習它的工作流工具。
公司的伺服器都是PHP、JSP、Python、.net製作的,nodejs做http服務是很少的。但是,每一個前端必須會Nodejs,因為之後用的所有的東西webpack、babel、gulp、grunt等等都是基於nodejs的。