什么是Node.js
官網介紹:
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境。
Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。
Node.js 的包管理器 npm,是全球最大的開源庫生態系統。
Node.js不是一個語言,也不是一個庫,更不是一個框架。只是一個運行環境,也就是平台。在Node.js這個平台上,我們可以使用JavaScript 來編寫程序,實現相應的功能。
使用Node.js 可以輕松地進行服務器端應用開發,PHP、Python、Ruby能做的事情,Node.js幾乎都能做,而且可以做得更好。那么既然已經有了PHP等后台語言,為什么還需要Node.js?這里我們要知道一個理念——任何一個有點小規模的產品,都不會只使用一門技術或一種語言,結合多種語言來實現,然后在不同的場景、需求上,使用相應的語言和技術去實現。目的就是為了提高產品的性能。每一張語言都有自己的優缺點,我們要利用好它們的優點。
我們在考慮是否應該使用這門語言的時候,就是要搞清楚它的優缺點。時效性要求比較高的應用,Node.js是最佳的。
Node.js 初體驗
官網下載安裝好Node.js,接下來我們來初步感受一下。
Hello world
打開cmd窗口,輸入node
命令,就可以進入Node.js的運行環境,在這里任何的JavaScript
代碼都可以編寫並執行,除了BOM 和 DOM 的內容。如果輸入了 BOM 和 DOM 的一些內容,在 Node 平台就會出錯。但是,node 平台也提供了一個全局對象——console
。
順便說一句,像這種 cmd 的窗口,有一個專有名詞 REPL
- R: read,讀取,等待用戶的輸入
- E: eval,執行代碼,輸入代碼完成之后,按回車鍵,可以執行代碼
- P: print,打印,輸出結果
- L: loop,循環,重復這個過程
執行js文件
上述的命令窗口模式操作node,平時很少會那樣用。我們還是用編輯器編寫js代碼,然后會通過cmd窗口來執行這個文件。
過程也很簡單,不需要進入node的REPL
環境,打開cmd
窗口,輸入 “node + 文件名(完整路徑)” 執行即可。
搭建服務器
下面我們用Node.js
來搭建HTTP服務器,
第一步:編寫server.js文件如下:
<!--創建一個HTTP服務器-->
// 載入http核心模塊
const http = require("http");
// 創建一個server對象
const server = http.createServer((req,res)=>{
// 通過res對象,輸出一些內容
res.writeHead(200,{"Content-Type":"text/html;charset=utf-8"});
res.write("<h1>http服務器</h1>");
res.write("<p>使用Node.js創建一個http服務器</p>");
res.end();
});
// 開啟server的監聽
server.listen(3000,()=>{
console.log("http server is listening in port 3000...");
});
第二步:打開cmd窗口,執行server.js文件
第三步:在瀏覽器中,輸入 localhost:3000
訪問,即可看到結果頁面。
模塊機制
在Node.js中,所謂的模塊,其實就是一個文件。一般而言,就是js文件/json文件。一個文件就是一個模塊,模塊是Node.js應用程序的基本組成部分。
模塊分類
簡單划分,可以將Node.js中的模塊分成兩大類:
- 系統模塊(核心模塊)
- 用戶模塊
其中,系統模塊是Node.js自帶的模塊,比如http、fs、net、url、path等,可以直接使用。核心模塊,Node.js中是內置好的。
用戶模塊不是Node.js本身的,又可以分為兩種:
- 第三方模塊,一些比較通用的,但是Node.js自身沒有提供的,這一類數量很龐大
- 自定義模塊,通常是在當前項目中,需要根據需求自己編寫的js代碼
加載模塊
如何在Node.js中加載模塊呢?
根據模塊類型的不同,加載的方式略有不同。相同的是,都會使用 require
函數。格式:require(模塊路徑);
模塊路徑的寫法,根據模塊類型的不同,寫法也不同:
- 核心模塊和第三方模塊的寫法一樣,只需要寫上模塊名即可。
- 自定義模塊,需要使用相對路徑來引入,必須使用
./
或者../
開頭。
(1)核心模塊的載入
核心模塊是Node.js自帶的,本身就具備的,直接載入就可以使用。
(2)第三方模塊的載入
首先,需要保證有一個第三方模塊,需要先安裝第三方模塊,使用npm 命令安裝即可。
npm install 模塊名。
本地安裝的時候,需要先進入對應的目錄,使用命令來安裝。
雖然,加載模塊的寫法和核心模塊一樣,但是原理不太一樣。如果是第三方模塊,它最加載的時候,一定會在當前目錄下,尋找 node_modules文件夾,在里面找對應的這模塊,如果找到,就直接使用。如果沒有找到,則會到父級目錄中找node_modules文件夾,找是否有該模塊,重復這個過程,直到根目錄。如果到根目錄,也沒有找到,就會報錯。
(3)加載自定義模塊
先創建一個模塊,其實就是一個js文件:mymodule.js
然后,使用require引入,const myModule = require('./mymodule.js');
如果沒有使用相對路徑,會報錯。
還有幾個細節需要注意一下:
- 模塊就是文件,一般就是js文件,js文件是有后綴的,后綴是可以省略的,核心模塊和第三方模塊必須省略,自定義模塊比較隨意,兩種都行。
- 如果沒有寫后綴,
require
方法在加載的時候,當文件不帶后綴,Node.js會依照 目錄 ->.js-> .json-> .node 的順序進行查找,如果所有模塊路徑都找不到該文件,則拋出異常。
自定義模塊的實現
默認情況下,任何一個模塊,被載入時,得到的是一個空對象,就是 module.exports
;我們可以直接在js文件中使用module.exports
。
在Node.js中,還有一個對象exports
,它實際上是module.exports
的一個引用,相當於exports = module.exports
。
如果使用的是module.exports
,那么直接賦值即可,如果使用的是exports
,不能直接賦值,原因很簡單嘛,又涉及到了對象數值的傳遞了。所以小結一下:
任何一個模塊(js文件)被 require
時,返回的是 module.exports
對象,默認為空。Node.js為了方便,提供了一個exports
對象,指向module.exports
,相當於執行了exports=module.exports
。如果需要提供對外接口,需要給module.exports
賦值為一個新的對象,或者使用exports.屬性名=值
的形式。
為了保險起見,我們可以寫成:exports = module.exports = {}
。
Node.js進行Web開發的核心
Server 對象
作用:用於創建服務器對象,提供HTTP服務,在Node.js中,Server對象充當了HTTP服務器的角色。它提供了一個監聽端口的底層套接字和接收請求,然后發送響應給客戶端鏈接的處理程序。
創建對象:http.createServer();
核心方法:listen;啟動監聽,啟動 http 服務,提供給用戶來訪問,有一個關鍵參數port,指定監聽的端口
重要事件:request:接收請求時觸發,傳遞兩個參數,IncommingMessage 對象和 ServerResponse 對象;
listening:調用listen 時觸發,同理也可以作為listen的回調函數來進行綁定。
實際上,用戶輸入url打開網頁就是一個請求事件,如果我們需要響應用戶的請求,那么必須先注冊該事件。需要對server綁定一個request事件,在Node.js中,綁定事件使用 on 來完成。為了簡化代碼的書寫,Node.js將注冊request事件,使用回調函數的方式來實現:
const server = http.createServer();
server.on('request',(req,res)=>{...});
簡化成
const server = http.createServer((req,res)=>{...});
ServerResponse 對象
在剛才的 createServer 的回調函數中,使用了 res 對象,這個對象實際上就是 ServerResponse 對象,服務器端的響應對象。
作用:用來寫http響應,包括響應狀態行、響應頭信息和響應正文。
創建:是自動創建的,作為回調函數的參數,自動創建好的一個對象。在createServer方法中的回調函數中,作為第二個參數來出現的。
重要屬性和方法:
- writeHead用於寫 響應狀態行和響應頭。只能調用一次
- write負責寫 響應正文。可以調用多次
- end,響應完畢,只能調用一次,必須要調用。
- setHeader,也可以通過這個方法設置頭信息
- getHeader,用來獲取頭信息的。
IncommingMessage 對象
作用:要理解其作用,需要看看HTTP請求和響應的過程:瀏覽器到服務器端的請求過程,作為服務器端需要獲取來自客戶端的信息;服務器端到瀏覽器端的響應過程,作為瀏覽器端也需要獲取來自服務器的一些信息。需要有一個對象,來表示這個信息,這個對象就是 incomingMessage對象。簡單來說,它的作用就是獲取對方(瀏覽器端或者服務器端)的一些信息。
如何創建:不需要手動創建,也是作為回調函數的參數出現的。站在服務器端的角度來看,這個incomingMessage 就是 createSever回調函數中的req參數。
重要的屬性和方法:
- httpVersion:請求/響應的HTTP版本
- headers:請求/響應頭信息
- rawHeaders:原始請求/響應頭信息
- method:請求方式,僅對Server獲得到的請求(request)有效
- url:請求的url字符串,僅對Server獲得到的請求(request)有效
- statusCode:響應狀態碼,僅對從ClientRequest獲得響應(response)有效
重要事件:
- data:數據傳遞時觸發
- end:沒有更多數據提供時觸發
由Readable Stream 提供了一下接口,然后 IncomingMessage實現了這些接口
ClientRequest 對象
什么是ClientRequest對象呢?就是客戶端請求對象,表示一個已經產生且正在進項中的HTTP請求。
使用Node創建Web客戶端,需要引入http模塊。
如何創建:有兩種方法,都是http對象的方法
-
request
-
get
const cr = http.request(); const cr = http.get();
核心方法:
- write:把一個正文寫入請求;
- end:完成請求,也可以寫入數據,end方法,完成請求的動作,針對request方法創建的對象而言。
對於get請求,請求正文是空的,不用寫,此時,不需要write方法;對於post請求,請求正文是需要寫的,此時,需要寫write方法。
重要事件:response,需要注冊一個響應事件,當發出請求后,需要監聽來自服務器端的響應。
在響應時的監聽器中,有一個重要的對象 IncommingMessage 對象,這個對象有兩個重要的事件。
- data,數據傳遞時觸發,
- end,沒有更多數據提供時觸發
使用這兩個事件就可以獲取來自服務器端的響應內容。代碼如下:
const http = require('http');
<!--創建一個ClientRequest對象-->
const cr = http.request({host:'www.baidu.com'});
//注冊response事件
cr.on('response',(res)=>{
let str = '';
//注冊data事件
res.on('data',(chunk)=>{
str += chunk;
});
res.on('end',()=>{
console.log(str);
});
});
//發出請求
cr.end();
針對上述過程,我們還可以使用get方法來簡化,其中,get方法,會自動發出請求,不需要調用end方法。
const http = require('http');
http.get('http://www.baidu.com',(res)=>{
let str = "";
res.on('data',(chunk)=>{
str += chunk;
});
res.on('end',()=>{
console.log(str);
});
});
利用這個可以搞一下爬蟲,爬一下網絡資源。
簡單應用
URL路由
URL:Uniform Resource Locator。統一資源定位器。還有一個就是URI(統一資源標識符),其中的I是指identifier。URL是基於URI的。在互聯網中,任何一個資源(html、css、js、img、動畫、視頻、音頻、word)需要保證它的唯一性。可以給每一個資源指定一個唯一的URL。簡單來說,URL就是我們常說的網址。
在web當中,用戶輸入不同的URL,服務器就接收到這個信息,需要處理這個信息,根據不同的請求,返回相應的內容。這個過程就是URL路由。我們可以通過req對象(IncomingMessage對象)中的url屬性,來獲取相關的信息,並進行處理。
Node.js提供了一個url模塊,可以解析url,得到更為詳細的信息。url.parse(url)
得到一個對象,里邊包含整個url中的各種信息。
代碼片段:
瀏覽器輸入 localhost:3000/user?username=admin&pwd=123
路由處理 let realUrl = "http://"+ req.headers.host + req.url;
let urlObj = url.parse(realUrl);//得到查詢字符串query : 'username=admin&pwd=123'
解析查詢字符串
打開一個網頁,很多情況下路徑名不發生變化,改變查詢字符串,就會顯示不同內容;附加的一些信息,需要根據這些信息,顯示不同的內容給用戶。
格式:?鍵1=值1&鍵2=值2&鍵n=值n
使用url.parse方法解析url之后,可以拿到查詢字符串
Node.js提供了querystring 模塊,其中有 parse 方法,可以將字符串解析成對象;這樣就可以直接通過屬性來獲取相對應的值。
代碼片段:
瀏覽器輸入 localhost:3000/user?username=admin&pwd=123
const querystring = require('querystring');
let realUrl = "http://"+ req.headers.host + req.url;
let urlObj = url.parse(realUrl);//得到查詢字符串query : 'username=admin&pwd=123'
let qObj = querystring.parse(urlObj.query);//得到{username: 'admin', pwd: '123'}
如何載入靜態頁面
在Node.js中,提供一個 web 服務器,有三種方式:
- 手動實現:結合 fs 文件操作;
- 第三方庫實現:http-server
- 框架實現:express
看一下手動實現:
第一步:准備一個靜態的HTML頁面index.html;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h1>載入靜態頁面</h1>
<p>可以結合fs操作,實現載入靜態頁面</p>
<img src="xx.jpg" alt="">
</body>
</html>
第二步:編碼,創建一個 web 服務器,當用戶輸入url,需要顯示靜態頁面;在這個過程中,由於圖片和CSS都是獨立的文件,對於它們,其實瀏覽器分別發送了單獨的請求,而不是像之前那樣直接去讀取。所以,我們要在服務器端對這些靜態文件進行處理,並通過res返回給瀏覽器端。
const http = require('http');
const fs = require('fs');
const url = require('url');
http.createServer( (req,res) => {
let realUrl = "http://" + req.headers.host + req.url;
let urlObj = url.parse(realUrl);
switch(urlObj.pathname) {
case "/" : //首頁
case "/index":
//使用fs讀取html文件,然后輸出
fs.readFile('index.html','utf8',(err,data) => {
if (err) throw err;
//讀取成功,寫入響應中
res.writeHead(200,{"content-type":"text/html;charset=utf-8"});
res.end(data);
});
break;
default:
//res.writeHead(200,{"content-type":"text/html;charset=utf-8"});
//res.end('頁面走丟了');
//將所有其他的資源,在這里做處理
if (fs.existsSync(__dirname + urlObj.pathname)) {
fs.readFile(__dirname + urlObj.pathname,(err,data) => {
if (err) throw err;
res.statusCode = 200;
res.end(data);
});
}
}
}).listen(3000,() => {
console.log('listening in port 3000...');
});
這里使用了一個魔術常量 __dirname
,表示當前代碼執行的路徑。
POST請求及響應
典型的動態交互,表單處理。通常就是post請求。看一下Node.js是如何實現post請求和回應的。
第一步,准備一個表單,注意:要寫獲取到表單數據,必須要為input添加name屬性,否則提交到服務器中的數據無法確定哪個是哪個,如下:
<form action="/signin" method="post" >
<ul>
<li>
<label for="">用戶名:</label>
<input type="text" name="username">
</li>
<li>
<label for="">密碼:</label>
<input type="password" name="password">
</li>
<li>
<label for=""></label>
<input type="submit" value="登錄">
</li>
</ul>
</form>
第二步,創建一個web server,載入這個表單;此時,會需要使用 IncommingMessage 對象的兩個事件 data 和 end,
switch (urlObj.pathname) {
case '/login' : //登錄頁面
fs.readFile('login.html','utf8',(err,data)=>{
if (err) throw err;
res.writeHead(200, {"content-type" : "text/html"});
res.end(data);
});
break;
case '/signin':
//console.log('提交到這兒了');
//需要接受用戶填寫的信息,利用IncomingMesaage對象的data和end事件
let str = "";
req.on('data', (chunk)=> {
str += chunk;
} );
req.on('end',()=>{
//表示提交完畢,str就是提交的數據了
console.log(str); //username=admin&password=1234
//利用querystring對象的parse方法進行解析
let userObj = querystring.parse(str);
res.writeHead(200,{"content-type":"text/html;charset=utf-8"});
res.write(`<p>你輸入的用戶名是 ${userObj.username}</p>`);
res.write(`<p>你輸入的密碼是 ${userObj.password}</p>`);
res.end();
});
break;
default:
break;
}
}).listen(3005, () => {
console.log('listening in port 3005');
});
需要注冊兩個事件,data和end。數據提交的時候,並不是一次提交完畢,而是一塊一塊的提交。每提交一塊,就觸發一次,需要不停的累加。直到end觸發了,說明提交完成了。
小結
以上是Node.js的基礎介紹,接下來的學習更多的是不同模塊的應用,和框架的學習了(express)。