者 | 糖果candy
導讀
introduction
如果你是一個前端程序員,你不懂得像PHP、Python或Ruby等動態編程語言,然后你想創建自己的服務,那么Node.js是一個非常好的選擇。
Node.js 是運行在服務端的 JavaScript,如果你熟悉Javascript,那么你將會很容易學會Node.js。
當然,如果你是后端程序員,想部署一些高性能的服務,那么學習Node.js也是一個非常好的選擇。
全文6723字,預計閱讀時間17分鐘。
GEEK TALK
01
什么是Node.js?
我們先看一下官方對Node.js的定義:Node.js是一個基于V8 JavaScript引擎的JavaScript運行時環境。
但是這句話可能有點籠統:
1、什么是JavaScript運行環境?
2、為什么JavaScript需要特別的運行環境呢?
3、什么又是JavaScript引擎?
4、什么是V8?
帶著這些疑問我們先了解一下nodejs的歷史,在 Node.js 出現之前,最常見的 JavaScript 運行時環境是瀏覽器,也叫做 JavaScript 的宿主環境。瀏覽器為 JavaScript 提供了 DOM API,能夠讓 JavaScript 操作瀏覽器環境(JS 環境)。
2009 年初 Node.js 出現了,它是基于 Chrome V8 引擎開發的 JavaScript 運行時環境,所以 Node.js 也是 JavaScript 的一種宿主環境。而它的底層就是我們所熟悉的 Chrome 瀏覽器的 JavaScript 引擎,因此本質上和在 Chrome 瀏覽器中運行的 JavaScript 并沒有什么區別。但是,Node.js 的運行環境和瀏覽器的運行環境還是不一樣的。
通俗點講,也就是說Node.js基于V8引擎來執行JavaScript的代碼,但是不僅僅只有V8引擎。
我們知道V8可以嵌入到任何C++應用程序中,無論是Chrome還是Node.js,事實上都是嵌入了V8引擎來執行JavaScript代碼,但是在Chrome瀏覽器中,還需要解析、渲染HTML、CSS等相關渲染引擎,另外還需要提供支持瀏覽器操作的API、瀏覽器自己的事件循環等。
另外,在Node.js中我們也需要進行一些額外的操作,比如文件系統讀/寫、網絡IO、加密、壓縮解壓文件等操作。
那么接下來我們來看一下瀏覽器是如何解析渲染的。
GEEK TALK
02
瀏覽器是怎么渲染一個頁面的?
瀏覽器渲染一個網頁,簡單來說可以分為以下幾個步驟:
相信看到這里,大家對瀏覽器怎么渲染出一個頁面已經有了大致的了解。頁面的繪制其實就是瀏覽器將 HTML 文本轉化為對應頁面幀的過程,頁面的內容及渲染過程與第一步拿到的 HTML 文本是緊密相關的。
GEEK TALK
03
事件循環和異步IO
首先要了解事件循環是什么?那么我們先了解進程和線上的概念。
每一個進程中都會啟動一個線程用來執行程序中的代碼,這個線程稱為主線程。
舉例子:比如工廠相當于操作系統,工廠里的車間相當于進程,車間里的工人相當于是線程,所以進程相當于是線程的容器。
那么瀏覽器是一個進程嗎?它里面是只有一個線程嗎?
目前瀏覽器一般都是多進程的,一般開啟一個tab就會開啟一個新的進程,這個是防止一個頁面卡死而造成的所有頁面都無法響應,整個瀏覽器需要強制退出。
其中每一個進程當中有包含了多個線程,其中包含了執行js代碼的線程。
js代碼是在一個單獨的線程中執行的,單線程同一時間只能做一件事,如果這件事非常耗時,就意味著當前的線程會被阻塞,瀏覽器時間循環維護兩個隊列:宏任務隊列和微任務隊列。
那么事件循環對于兩個隊列的優先級是怎么樣的呢?
new promise()是同步,promise.then,promise.catch,resolve,reject 是微任務。
GEEK TALK
04
使用事件驅動程序
Node.js 使用事件驅動模型,當web server接收到請求,就把它關閉然后進行處理,然后去服務下一個web請求。
當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給用戶。
這個模型非常高效可擴展性非常強,因為webserver一直接受請求而不等待任何讀寫操作。(這也被稱之為非阻塞式IO或者事件驅動IO)
在事件驅動模型中,會生成一個主循環來監聽事件,當檢測到事件時觸發回調函數。
整個事件驅動的流程就是這么實現的,非常簡潔。有點類似于觀察者模式,事件相當于一個主題(Subject),而所有注冊到這個事件上的處理函數相當于觀察者(Observer)。
Node.js 有多個內置的事件,我們可以通過引入 events 模塊,并通過實例化 EventEmitter 類來綁定和監聽事件,如下實例:
// 引入 events 模塊
var events = require('events');
// 創建 eventEmitter 對象
var eventEmitter = new events.EventEmitter();
以下程序綁定事件處理程序:
// 綁定事件及事件的處理程序
eventEmitter.on('eventName', eventHandler);
我們可以通過程序觸發事件:
// 觸發事件
eventEmitter.emit('eventName');
實例
創建 main.js 文件,代碼如下所示:
// 引入 events 模塊var events = require('events');
// 創建 eventEmitter 對象var eventEmitter = new events.EventEmitter();
// 創建事件處理程序var connectHandler = functionconnected() {
console.log('連接成功~~~');
// 觸發 data_received 事件
eventEmitter.emit('data_received');
}
// 綁定 connection 事件處理程序
eventEmitter.on('connection', connectHandler);
// 使用匿名函數綁定 data_received 事件
eventEmitter.on('data_received', function(){
console.log('數據接收完畢。');
});
// 觸發 connection 事件
eventEmitter.emit('connection');
console.log("程序執行完畢。");
接下來讓我們執行以上代碼:
$ node main.js
連接成功~~~
數據接收完畢。
程序執行完畢。
GEEK TALK
05
Node.js架構以及與瀏覽器的區別
上圖是 Node.js 的基本架構,我們可以看到,(Node.js 是運行在操作系統之上的),它底層由 V8 JavaScript 引擎,以及一些 C/C++ 寫的庫構成,包括 libUV 庫、c-ares、llhttp/http-parser、open-ssl、zlib 等等。
其中,libUV 負責處理事件循環,c-ares、llhttp/http-parser、open-ssl、zlib 等庫提供 DNS 解析、HTTP 協議、HTTPS 和文件壓縮等功能。
在這些模塊的上一層是中間層,中間層包括Node.js Bindings、Node.js Standard Library以及C/C++ AddOns。Node.js Bindings層的作用是將底層那些用 C/C++ 寫的庫接口暴露給 JS 環境,而Node.js Standard Library是 Node.js 本身的核心模塊。至于C/C++ AddOns,它可以讓用戶自己的 C/C++ 模塊通過橋接的方式提供給Node.js。
中間層之上就是 Node.js 的 API 層了,我們使用 Node.js 開發應用,主要是使用 Node.js 的 API 層,所以 Node.js 的應用最終就運行在 Node.js 的 API 層之上。
總結一下:Node.js 系統架構圖,主要就是application、V8 javascript 引擎、Node.js bindings, libuv這4個部分組成的。
瀏覽器中的事件循環是根據HTML5規范來實現的,不同的瀏覽器可能有不同的實現,而node中是libuv 實現的
因為 Node.js 不是瀏覽器,所以它不具有瀏覽器提供的 DOM API。
也有一些對象是 Node.js 和瀏覽器共有的,如 JavaScript 引擎的內置對象,它們由 V8 引擎提供。常見的還有:
此外,還有一些方法不屬于引擎內置 API,但是兩者都能實現,比如 setTimeout、setInterval 方法,Console 對象等等。
5.1 阻塞IO和非阻塞IO
如果我們希望在程序中對一個文件進行操作,那么我們就需要打開這個文件:通過文件描述符。
我們思考:JavaScript可以直接對一個文件進行操作嗎?
看起來是可以的,但是事實上我們任何程序中的文件操作都是需要進行系統調用(操作系統的文件系統);事實上對文件的操作,是一個操作系統的IO操作(輸入、輸出)。
操作系統為我們提供了阻塞式調用和非阻塞式調用:
所以我們開發中的很多耗時操作,都可以基于這樣的 非阻塞式調用:
比如網絡請求本身使用了Socket通信,而Socket本身提供了select模型,可以進行非阻塞方式的工作;
比如文件讀寫的IO操作,我們可以使用操作系統提供的基于事件的回調機制。
5.2 非阻塞IO的問題
但是非阻塞IO也會存在一定的問題:我們并沒有獲取到需要讀取(我們以讀取為例)的結果,那么就意味著為了可以知道是否讀取到了完整的數據,我們需要頻繁的去確定讀取到的數據是否是完整的。
這個過程我們稱之為輪訓操作。
那么這個輪訓的工作由誰來完成呢?
如果我們的主線程頻繁的去進行輪訓的工作,那么必然會大大降低性能,并且開發中我們可能不只是一個文件的讀寫,可能是多個文件,而且可能是多個功能:網絡的IO、數據庫的IO、子進程調用。
libuv提供了一個線程池(Thread Pool):線程池會負責所有相關的操作,并且會通過輪訓等方式等待結果。當獲取到結果時,就可以將對應的回調放到事件循環(某一個事件隊列)中。事件循環就可以負責接管后續的回調工作,告知JavaScript應用程序執行對應的回調函數。
5.3 阻塞和非阻塞,同步和異步的區別?
首先阻塞和非阻塞是對于被調用者來說的;在我們這里就是系統調用,操作系統為我們提供了阻塞調用和非阻塞調用,同步和異步是對于調用者來說的。
Libuv采用的就是非阻塞異步IO的調用方式。
5.4 Node事件循環的階段
我們最前面就強調過,事件循環像是一個橋梁,是連接著應用程序的JavaScript和系統調用之間的通道:
無論是我們的文件IO、數據庫、網絡IO、定時器、子進程,在完成對應的操作后,都會將對應的結果和回調
函數放到事件循環(任務隊列)中;
事件循環會不斷的從任務隊列中取出對應的事件(回調函數)來執行;
但是一次完整的事件循環Tick分成很多個階段:
定時器(Timers):本階段執行已經被 setTimeout() 和 setInterval() 的調度回調函數。
待定回調(Pending Callback):對某些系統操作(如TCP錯誤類型)執行回調,比如TCP連接時接收idle, prepare:僅系統內部使用。
輪詢(Poll):檢索新的 I/O 事件;執行與 I/O 相關的回調;
檢測:setImmediate() 回調函數在這里執行。
關閉的回調函數:一些關閉的回調函數,如:socket.on('close', ...)。
5.5 Node事件循環的階段圖解
GEEK TALK
06
Node.js常見的內置模塊與全局變量
如想了解更多全局對象可參考以下鏈接 :https://m.runoob.com/nodejs/nodejs-global-object.html
作者:糖果candy
來源:微信公眾號:百度Geek說
出處:https://mp.weixin.qq.com/s/QI9l4kKCbJxifNZt3QP3Lg
Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環境,使用了一個事件驅動、非阻塞式 I/O 模型,讓 JavaScript 運行在服務端的開發平臺。
官方地址:https://nodejs.org/en
中文地址:https://nodejs.org/zh-cn
代碼初體驗:
console.log("hello NodeJS")
// 1.進入到對應 js 文件的目錄下
// 2.執行 node 1-hello.js
// 3.輸出:hello NodeJS
示例代碼地址:https://github.com/chenyl8848/node.js-study
Buffer 是一個類似于數組的對象,用于表示固定長度的字節序列。Buffer 本質是一段內存空間,專門用來處理二進制數據。
Node.js 中創建 Buffer 的方式主要如下幾種:
Buffer.alloc
// 創建了一個長度為 10 字節的 Buffer,相當于申請了 10 字節的內存空間,每個字節的值為 0
// 結果為 <Buffer 00 00 00 00 00 00 00 00 00 00>
let buf_1 = Buffer.alloc(10);
Buffer.allocUnsafe
// 創建了一個長度為 10 字節的 Buffer,Buffer 中可能存在舊的數據, 可能會影響執行結果,所以叫 unsafe
let buf_2 = Buffer.allocUnsafe(10);
Buffer.from
// 通過字符串創建 Buffer
let buf_3 = Buffer.from('hello');
// 通過數組創建 Buffer
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
可以借助 toString 方法將 Buffer 轉為字符串
// buffer 與字符串的轉換
const buffer = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
// 默認使用 UTF-8 的編碼格式
console.log(buffer.toString())
注意:toString 默認是按照 utf-8 編碼方式進行轉換的。
Buffer 可以直接通過 [] 的方式對數據進行處理。
const buffer = Buffer.from('hello');
// 二進制:1101000
console.log(buffer[0].toString(2))
// 修改 buffer
buffer[0] = 95
console.log(buffer.toString())
// 溢出 如果修改的數值超過 255 ,則超過 8 位數據會被舍棄
const buffer = Buffer.from('hello')
// 會舍棄高位的數字,因為八位的二進制最高值為 255 0001 0110 1001 => 0110 1001
buffer[0] = 361
console.log(buffer)
// 中文 一個 utf-8 的字符 一般 占 3 個字節
const buffer = Buffer.from('你好')
console.log(buffer)
注意:
如果修改的數值超過255,則超過8位數據會被舍棄 一個 utf-8 的字符一般占3個字節
fs 全稱為 file system,稱之為文件系統,是 Node.js 中的內置模塊,可以對計算機中的磁盤進行操作。
文件寫入就是將數據保存到文件中,有如下的 API
方法 | 說明 |
writeFile | 異步寫入 |
writeFileSync | 同步寫入 |
appendFile/appendFileSync | 追加寫入 |
createWriteStream | 流式寫入 |
語法: fs.writeFile(file, data, [options], callback)
參數說明:
返回值: undefined
代碼示例:
// 1.導入 fs 模塊
const fs = require('fs')
// 2.寫入文件
// writeFile 異步寫入,四個參數:1.文件路徑 2.寫入內容 3.配置信息 4.回調函數
// 文件寫入成功
fs.writeFile('./座右銘.txt', '封狼居胥,禪于姑衍,飲馬瀚海', error => {
// errror 為 null就是寫入成功
if (error) {
console.log('文件寫入失敗')
return;
}
console.log('文件寫入成功')
});
語法: fs.writeFileSync(file, data, [options]) 參數說明:
// 1.導入 fs 模塊
const fs = require('fs')
// 2.寫入文件
// 同步寫入,沒有回調函數
fs.writeFileSync('./座右銘.txt', '封狼居胥,禪于姑衍,飲馬瀚海,燕石勒然')
Node.js 中的磁盤操作是由其他線程完成的,結果的處理有兩種模式:
同步處理:JavaScript 主線程會等待其他線程的執行結果,然后再繼續執行主線程的代碼,效率較低
異步處理:JavaScript 主線程不會等待其他線程的執行結果,直接執行后續的主線程代碼,效率較好
appendFile 作用是在文件尾部追加內容,appendFile 語法與 writeFile 語法完全相同
語法:
返回值: 二者都為 undefined
代碼示例:
// 1.引入 fs 模塊
const fs = require('fs');
const content = '\r\n但使龍城飛將在,不教胡馬度陰山';
// fs.appendFile('./座右銘.txt', content, error => {
// // errror 為 null就是寫入成功
// if (error) {
// console.log('文件追加寫入失敗')
// return;
// }
// console.log('文件追加寫入成功');
// })
// 同步文件追加寫入
// fs.appendFileSync('./座右銘.txt', content)
// 使用 writeFile 的方式追加文件寫入
fs.writeFile('./座右銘.txt', content, {flag: 'a'}, error => {
if (error) {
console.log('文件追加寫入失敗')
return;
}
console.log('文件追加寫入成功')
})
console.log('hello world')
語法: fs.createWriteStream(path, [options])
參數說明:
返回值: Object
代碼示例:
// 1.導入 fs 模塊
const fs = require('fs');
// 2.創建寫入流對象
const writeStream = fs.createWriteStream('./觀書有感.txt');
// 3.寫入內容
writeStream.write('半畝方塘一鑒開\r\n')
writeStream.write('天光云影共徘徊\r\n')
writeStream.write('問渠那得清如許\r\n')
writeStream.write('為有源頭活水來\r\n')
// 4.關閉通道 不是必須
// writeStream.close();
程序打開一個文件是需要消耗資源的,流式寫入可以減少打開關閉文件的次數。
流式寫入方式適用于大文件寫入或者頻繁寫入的場景, writeFile 適合于寫入頻率較低的場景。
文件讀取顧名思義,就是通過程序從文件中取出其中的數據,有如下幾種 API:
方法 | 說明 |
readFile | 異步讀取 |
readFileSync | 同步讀取 |
createReadStream | 流式讀取 |
語法: fs.readFile(path, [options], callback)
參數說明:
返回值: undefined
代碼示例:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.異步讀取
fs.readFile('./座右銘.txt', (error, data) => {
if (error) {
console.log('文件讀取錯誤')
return
}
console.log(data.toString())
})
語法: fs.readFileSync(path, [options])
參數說明:
返回值: string | Buffer
代碼示例:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.同步讀取
let data = fs.readFileSync('./座右銘.txt');
console.log(data.toString())
語法: fs.createReadStream(path, [options])
參數說明:
返回值:Object
代碼示例:
// 1.導入 fs 模塊
const fs = require('fs')
// 2.創建讀取流對象
const rs = fs.createReadStream('./觀書有感.txt');
// 3.綁定 data 事件
rs.on('data', chunk => {
// chunk:塊兒、大塊兒
console.log(chunk)
console.log(chunk.length)
console.log(chunk.toString())
})
// 4.結束事件(可選)
rs.on('end', () => {
console.log('文件讀取完成')
})
在 Node.js 中,可以使用 rename 或 renameSync 來移動或重命名文件或文件夾
語法:
參數說明:
代碼示例:
// 1.導入 fs 模塊
const fs = require('fs');
// 2.文件重命名
// fs.rename('./座右銘-2.txt', './西漢名將.txt', error => {
// if (error) {
// console.log('文件重命名失敗')
// return ;
// }
// console.log('文件重命名成功')
// })
// 3.文件移動
// 文件夾如果不存在,會報錯誤 Error: ENOENT: no such file or directory
fs.rename('./西漢名將.txt', './文件/西漢名將.txt', error => {
if (error) {
console.log(error, '移動文件出錯');
return ;
}
console.log('操作成功')
})
在 Node.js 中,可以使用 unlink 或 unlinkSync 或 rm 或 rmSync 來刪除文件
語法:
參數說明:
代碼示例:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.調用 unlink 方法
// unlinkSync:同步刪除
// fs.unlink('./座右銘-3.txt', error => {
// if (error) {
// console.log('刪除文件錯誤', error)
// return;
// }
// console.log('刪除文件成功')
// })
// 3.調用 rm 方法
// rmSync:同步刪除
fs.rm('./文件/西漢名將.txt', error => {
if (error) {
console.log('文件刪除失敗', error)
return;
}
console.log('文件刪除成功')
})
在 Node.js 中可以通過如下 API 對文件夾進行創建、讀取、刪除等操作
方法 | 說明 |
mkdir / mkdirSync | 創建文件夾 |
readdir / readdirSync | 讀取文件夾 |
rmdir / rmdirSync | 刪除文件夾 |
在 Node.js 中,可以使用 mkdir 或 mkdirSync 來創建文件夾
語法:
參數說明:
示例代碼:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.創建文件夾
// mkdir make:制作 directory:目錄
fs.mkdir('./html', error => {
if (error) {
console.log('創建目錄失敗', error)
return;
}
console.log('創建目錄成功')
})
// 3.遞歸創建文件夾
fs.mkdir('./a/b/c', {
recursive: true
}, error => {
if (error) {
console.log("遞歸創建文件夾失敗", error)
return;
}
console.log('遞歸創建文件夾成功')
})
// 4.遞歸同步創建文件夾
fs.mkdirSync('./a/b/c', {recursive: true});
在 Node.js 中,可以使用 readdir 或 readdirSync 來讀取文件夾
語法:
參數說明:
示例代碼:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.讀取文件夾
// readdir read:讀取 dir:directory 目錄
fs.readdir('./', (error, data) => {
if (error) {
console.log('讀取文件夾錯誤', error)
return;
}
// [
// '1-文件寫入.js',
// '2-追加寫入.js',
// '3-流式寫入.js',
// '4-文件讀取.js',
// '5-流式讀取.js',
// '6-練習-文件復制.js',
// '7-文件重命名與移動.js',
// '8-刪除文件.js',
// '9-文件夾操作.js',
// 'a',
// 'html',
// '座右銘.txt',
// '文件',
// '觀書有感.txt'
// ]
console.log(data)
})
//同步讀取
// let data = fs.readdirSync('./');
// console.log(data);
在 Node.js 中,可以使用 rmdir 或 rmdirSync 來刪除文件夾
語法:
示例代碼:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.刪除文件夾
// fs.rmdir('./文件', error => {
// if (error) {
// console.log('刪除文件夾失敗', error)
// return;
// }
// console.log('刪除文件夾成功')
// })
// 3.遞歸刪除文件夾
// 遞歸刪除文件夾失敗 [Error: ENOTEMPTY: directory not empty, rmdir 'E:\JavaEE\frontend\nodejs-study\2-fs文件系統\a']
// 不推薦使用
// fs.rmdir('./a', {recursive: true}, error => {
// if (error) {
// console.log('遞歸刪除文件夾失敗', error)
// return ;
// }
// console.log('遞歸刪除文件夾成功')
// })
// 推薦使用
fs.rm('./a', {recursive: true}, error => {
if (error) {
console.log('遞歸刪除文件失敗', error)
return;
}
console.log('遞歸刪除文件成功')
})
//同步遞歸刪除文件夾
fs.rmdirSync('./a', {recursive: true})
在 Node.js 中,可以使用 stat 或 statSync 來查看資源的詳細信息
語法:
參數說明:
示例代碼:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.stat 方法 status 的縮寫:狀態
fs.stat('./觀書有感.txt', (error, data) => {
if (error) {
console.log('操作失敗', error)
return;
}
// Stats {
// dev: 985301708,
// mode: 33206,
// nlink: 1,
// uid: 0,
// gid: 0,
// rdev: 0,
// blksize: 4096,
// ino: 281474979770305,
// size: 92,
// blocks: 0,
// atimeMs: 1684373309132.9426,
// mtimeMs: 1684289136772.1648,
// ctimeMs: 1684289136772.1648,
// birthtimeMs: 1684289136770.7136,
// atime: 2023 - 05 - 18 T01: 28: 29.133 Z,
// mtime: 2023 - 05 - 17 T02: 05: 36.772 Z,
// ctime: 2023 - 05 - 17 T02: 05: 36.772 Z,
// birthtime: 2023 - 05 - 17 T02: 05: 36.771 Z
// }
console.log(data)
console.log(data.isFile())
console.log(data.isDirectory())
})
// 3.同步獲取狀態
let data = fs.statSync('./觀書有感.txt');
console.log(data)
console.log(data.isFile())
console.log(data.isDirectory())
結果值對象結構:
fs 模塊對資源進行操作時,路徑的寫法有兩種:
相對路徑中所謂的當前目錄,指的是命令行的工作目錄,而并非是文件的所在目錄。所以當命令行的工作目錄與文件所在目錄不一致時,會出現一些 BUG。
__dirname 與 require 類似,都是 Node.js 環境中的 '全局' 變量 __dirname 保存著當前文件所在目錄的絕對路徑,可以使用 __dirname 與文件名拼接成絕對路徑
代碼示例:
let data = fs.readFileSync(__dirname + '/data.txt');
console.log(data);
使用 fs 模塊的時候,盡量使用 __dirname 將路徑轉化為絕對路徑,這樣可以避免相對路徑產生的 Bug
實現文件復制的功能
代碼示例:
/**
* 需求:
* 復制【座右銘.txt】
*/
// 1.引入 fs 模塊
const fs = require('fs');
// 全局對象,即 global 對象的屬性,無須聲明即可訪問。它用于描述當前 Node 進程狀態的對象,提供了一個與操作系統的簡單接口。
const process = require('process')
// 方式一:使用 readFile
// 2.讀取文件
// let data = fs.readFileSync('./座右銘.txt');
// // 3.寫入文件
// fs.writeFileSync('./座右銘-2.txt', data);
// // 查看系統耗費內存
// // rss: 19795968 字節
// console.log(process.memoryUsage())
// 方式二:使用流式操作
// 2.創建流式讀取
let rs = fs.createReadStream('./座右銘.txt');
// 3.創建流式寫入
let ws = fs.createWriteStream('./座右銘-3.txt');
// // 4.綁定 data 事件
// rs.on('data', chunk => {
// ws.write(chunk);
// })
// // 5.綁定 end 事件
// rs.on('end', () => {
// // rss: 20885504 字節 相比于同步的方式占用內存會比較小
// console.log(process.memoryUsage())
// })
// 4.使用 pipe(管道) 可直接復制
rs.pipe(ws)
path 模塊提供了操作路徑的功能,有如下幾個較為常用的 API:
方法 | 說明 |
path.resolve | 拼接規范的絕對路徑常用 |
path.sep | 獲取操作系統的路徑分隔符 |
path.parse | 解析路徑并返回對象 |
path.basename | 獲取路徑的基礎名稱 |
path.dirname | 獲取路徑的目錄名 |
path.extname | 獲得路徑的擴展名 |
代碼示例:
// 1.引入 path、fs 模塊
const path = require('path')
const fs = require('fs')
// 寫入文件
// fs.writeFileSync(__dirname + '/index.html', 'love')
// E:\JavaEE\frontend\nodejs-study\3-path模塊/index.html
// console.log(__dirname + '/index.html')
// 使用 path 解決
// E:\JavaEE\frontend\nodejs-study\3-path模塊\index.html
console.log(path.resolve(__dirname, './index.html'))
// E:\JavaEE\frontend\nodejs-study\3-path模塊\index.html
console.log(path.resolve(__dirname, 'index.html'))
// E:\index.html\test
console.log(path.resolve(__dirname, '/index.html', './test'))
// 分隔符
// \ windows:\ linux:/
console.log(path.sep)
// parse 方法 __dirname '全局變量'
// __filename '全局變量' 文件的絕對路徑
console.log(__filename)
let str = 'E:\\JavaEE\\frontend\\nodejs-study\\3-path模塊\\index.html';
// {
// root: 'E:\\',
// dir: 'E:\\JavaEE\\frontend\\nodejs-study\\3-path模塊',
// base: 'index.html',
// ext: '.html',
// name: 'index'
// }
console.log(path.parse(str))
// index.html
console.log(path.basename(str))
// E:\JavaEE\frontend\nodejs-study\3-path模塊
console.log(path.dirname(str))
// .html
console.log(path.extname(str))
代碼示例:
// 1.引入 http 模塊
const http = require('http')
// 2.創建 http 服務
const server = http.createServer((request, response) => {
// response.end 設置響應體 必須要返回,且只能返回一次
// 多個 response.end 報錯 Error [ERR_STREAM_WRITE_AFTER_END]: write after end
// response.end("hello world")
// response.end("hello world")
// 解決中文亂碼
response.setHeader('content-type', 'text/html;charset=utf-8')
response.end('你好,世界')
})
// 3.啟動服務并監聽端口
server.listen(9000, () => {
console.log('http 服務啟動成功')
})
http.createServer 里的回調函數的執行時機:當接收到 http 請求的時候,就會執行
瀏覽器請求對應端口 http://127.0.0.1:9000
response.setHeader('content-type', 'text/html;charset=utf-8')
想要獲取請求的數據,需要通過 request 對象
方法 | 說明 |
request.method | 請求方法 |
request.httpVersion | 請求版本 |
request.url | 請求路徑 |
require('url').parse(request.url).pathname | URL 路徑 |
require('url').parse(request.url, true).query | URL 查詢字符串 |
request.headers | 請求頭 |
request.on('data', function(chunk)) | 請求體 |
注意事項:
代碼示例:
// 1.引入 http 模塊
const http = require('http')
// 2.創建服務
const server = http.createServer((request, response) => {
// 獲取請求的方法
console.log(request.method)
// 獲取請求的 url
console.log(request.url)
// 獲取 http 協議版本號
console.log(request.httpVersion)
// 獲取請求頭
console.log(request.headers)
// 獲取請求主機地址
console.log(request.headers.host)
response.end("hello world")
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
代碼示例:
// 1.引入 http 模塊
const http = require('http')
// 2.創建服務
const server = http.createServer((request, response) => {
// 1.聲明一個變量
let body = ''
// 2.綁定 data 事件
request.on('data', chunk => {
body += chunk
})
// 3.綁定 end 事件
request.on('end', () => {
console.log(body)
response.end("hello world")
})
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
代碼示例:
// 1.引入 http 模塊
const http = require('http')
// 1.導入 url 模塊
const url = require('url')
// 2.創建服務
const server = http.createServer((request, response) => {
// request: http://127.0.0.1:9000/search?keyword=123&username=chen
// 2.解析 request.url
// let res1 = url.parse(request.url)
// Url {
// protocol: null,
// slashes: null,
// auth: null,
// host: null,
// port: null,
// hostname: null,
// hash: null,
// search: '?keyword=123&username=chen',
// query: 'keyword=123&username=chen',
// pathname: '/search',
// path: '/search?keyword=123&username=chen',
// href: '/search?keyword=123&username=chen'
// }
// console.log(res1)
// let res2 = url.parse(request.url, true)
// Url {
// protocol: null,
// slashes: null,
// auth: null,
// host: null,
// port: null,
// hostname: null,
// hash: null,
// search: '?keyword=123&username=chen',
// query: [Object: null prototype] {
// keyword: '123',
// username: 'chen'
// },
// pathname: '/search',
// path: '/search?keyword=123&username=chen',
// href: '/search?keyword=123&username=chen'
// }
// console.log(res2)
let res = url.parse(request.url, true)
// 請求路徑
// /search
console.log(res.pathname)
// 查詢字符串
// 123
console.log(res.query.keyword)
response.end('hello world')
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
代碼示例:
// 1.引入 http 模塊
const http = require('http')
// 2.創建服務
const server = http.createServer((request, response) => {
// request: http://127.0.0.1:9000/search?keyword=123&username=chen
// 第二個參數要寫完整 http://127.0.0.1 只寫 ip 會報錯
let url = new URL(request.url, 'http://127.0.0.1')
// URL {
// href: 'http://127.0.0.1/search?keyword=123&username=chen',
// origin: 'http://127.0.0.1',
// protocol: 'http:',
// username: '',
// password: '',
// host: '127.0.0.1',
// hostname: '127.0.0.1',
// port: '',
// pathname: '/search',
// search: '?keyword=123&username=chen',
// searchParams: URLSearchParams {
// 'keyword' => '123', 'username' => 'chen'
// },
// hash: ''
// }
// console.log(url)
// 請求路徑
console.log(url.pathname)
// 請求路徑參數
console.log(url.searchParams.get('keyword'))
response.end('hello world')
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
方法 | 說明 |
response.statusCode | 設置響應狀態碼 |
response.statusMessage | 設置響應狀態描述 |
response.setHeader('鍵名', '鍵值') | 設置響應頭信息 |
response.write('xx')response.end('xxx') | 設置響應體 |
write 和 end 的兩種使用情況:
// 1.write 和 end 的結合使用 響應體相對分散
response.write('xx');
response.write('xx');
response.write('xx');
//每一個請求,在處理的時候必須要執行 end 方法,且只能執行一次
response.end();
// 2.單獨使用 end 方法 響應體相對集中
response.end('xxx');
代碼示例:
// 1.引入 http 模塊
const http = require('http')
// 2.創建服務
const server = http.createServer((request, response) => {
// 1.設置響應狀態碼
// response.statusCode = 200
// response.statusCode = 404
// 2.設置響應信息 不常用
// response.statusMessage = 'hello world'
// 3.設置響應頭
response.setHeader('content-type', 'text/html;charset=utf-8')
response.setHeader('Server', 'Node.js')
response.setHeader('myHeader', 'myHeader')
response.setHeader('test', ['love', 'love', 'love'])
// 4.設置響應體
response.write('love\r\n')
response.write('love\r\n')
response.write('love\r\n')
response.write('love\r\n')
response.end('hello world')
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
需求: 當請求方式為 get 請求時,請求路徑為 /login 返回 login 當請求方式為 get 請求時,請求路徑為 /register 返回 register
代碼示例:
/**
* 需求:
* 當請求方式為 get 請求時,請求路徑為 /login 返回 login
* 當請求方式為 get 請求時,請求路徑為 /register 返回 register
*/
// 1.引入 http 模塊
const http = require('http')
// 2.創建服務
const server = http.createServer((request, response) => {
// 請求方式
let { method } = request
// 請求路徑
let { pathname } = new URL(request.url, 'http://127.0.0.1')
if (method === 'GET' && pathname === '/login') {
response.end('login')
} else if (method === 'GET' && pathname === '/register') {
response.end('register')
} else {
response.end('hello world')
}
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
需求: 回一個4行3列的表格,且要求表格有隔行換色效果,且點擊單元格能高亮顯示
代碼示例:
/**
* 需求:
* 返回一個4行3列的表格,且要求表格有隔行換色效果,且點擊單元格能高亮顯示
*/
// 1.引入 http 模塊
const http = require('http')
// 2.創建服務
const server = http.createServer((request, response) => {
response.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
td {
padding: 20px 40px;
}
table tr:nth-child(odd) {
background: rgb(179, 165, 201);
}
table tr:nth-child(even) {
background: #fcb;
}
table,
td {
border-collapse: collapse;
}
</style>
</head>
<body>
<table border="1">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</table>
<script>
//獲取所有的 td
let tds = document.querySelectorAll('td');
//遍歷
tds.forEach(item => {
item.onclick = function () {
this.style.background = '#222';
}
})
</script>
</body>
</html>
`)
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
代碼優化:
/**
* 需求:
* 返回一個4行3列的表格,且要求表格有隔行換色效果,且點擊單元格能高亮顯示
*/
// 1.引入 http 模塊
const http = require('http')
// 1.引入 fs 模塊
const fs = require('fs')
// 2.創建服務
const server = http.createServer((request, response) => {
// 2.讀取文件內容
let file = fs.readFileSync(__dirname + '/10-table.html')
response.end(file)
})
// 3.啟動服務并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
靜態資源是指內容長時間不發生改變的資源,例如圖片、視頻、CSS 文件、JS 文件、HTML 文件、字體文件等。
動態資源是指內容經常更新的資源,例如百度首頁、網易首頁、京東搜索列表頁面等。
網頁中的 URL 主要分為兩大類:相對路徑與絕對路徑
絕對路徑可靠性強,而且相對容易理解,在項目中運用較多
形式 | 特點 |
http://xxx.com/web | 直接向目標資源發送請求,容易理解,網站的外鏈會用到此形式 |
//xxx.com/web | 與頁面 URL 的協議拼接形成完整 URL 再發送請求,大型網站用的比較多 |
/web | 與頁面 URL 的協議、主機名、端口拼接形成完整 URL 再發送請求,中小型網站 |
相對路徑在發送請求時,需要與當前頁面 URL 路徑進行計算,得到完整 URL 后,再發送請求 例如當前網頁 URL 為 http://www.xxx.com/course/h5.html
形式 | 最終的 URL |
./css/app.css | http://www.xxx.com/course/css/app.css |
js/app.js | http://www.xxx.com/course/js/app.js |
../img/logo.png | http://www.xxx.com/img/logo.png |
../../mp4/show.mp4 | http://www.atguigu.com/mp4/show.mp4 |
媒體類型(通常稱為 Multipurpose Internet Mail Extensions 或 MIME 類型 )是一種標準,用來表示文檔、文件或字節流的性質和格式。
mime 類型結構: [type]/[subType] 例如: text/html text/css image/jpeg image/png application/json
http 服務可以設置響應頭 Content-Type 來表明響應體的 mime 類型,瀏覽器會根據該類型決定如何處理資源 下面是常見文件對應的 mime 類型:
{
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
}
說明:對于未知的資源類型,可以選擇 application/octet-stream 類型,瀏覽器在遇到該類型的響應時,會對響應體內容進行獨立存儲,也就是下載。
代碼示例:
/**
* 需求:
* 根據不同的請求路徑,返回不同的文件
*/
// 1.引入模塊
const http = require('http')
const fs = require('fs')
// 2.創建服務
const server = http.createServer((request, response) => {
// 獲取請求路徑
let { pathname } = new URL(request.url, 'https://127.0.0.1:9000')
if (pathname === '/') {
// 獲取要響應的文件
let data = fs.readFileSync(__dirname + '/11-table.html')
// 設置響應
response.end(data)
} else if (pathname === '/index.css') {
// 獲取要響應的文件
let data = fs.readFileSync(__dirname + '/index.css')
// 設置響應
response.end(data)
} else if (pathname === '/index.js') {
// 獲取要響應的文件
let data = fs.readFileSync(__dirname + '/index.js')
// 設置響應
response.end(data)
} else {
response.end(`<h1>404 Not Found</h1>`)
}
})
// 3.啟動服務,并監聽端口‘
server.listen(9000, () => {
console.log('服務啟動成功')
})
代碼優化:
/**
* 需求:
* 創建一個 HTTP 服務,端口為 9000,滿足如下需求
* GET /index.html 響應 page/index.html 的文件內容
* GET /css/app.css 響應 page/css/app.css 的文件內容
* GET /images/logo.png 響應 page/images/logo.png 的文件內容
*/
// 1.引入模塊
const http = require('http')
const fs = require('fs')
const path = require('path')
// 2.創建服務
const server = http.createServer((request, response) => {
// 獲取文件根路徑
let root = path.resolve(__dirname + '/page')
// 獲取文件路徑
let { pathname } = new URL(request.url, 'https://127.0.0.1:9000')
// 拼接文件路徑
let filePath = root + pathname
// 獲取文件
fs.readFile(filePath, (error, data) => {
// 設置字符集,解決打開文件中文亂碼的問題
response.setHeader('content-type', 'text/html;charset=utf-8')
if (error) {
response.end('讀取文件錯誤')
}
response.end(data)
return;
})
})
// 3.啟動服務并監聽端口
server.listen(9000, () => {
console.log('服務啟動成功')
})
代碼優化:
/**
* 需求:
* 創建一個 HTTP 服務,端口為 9000,滿足如下需求
* GET /index.html 響應 page/index.html 的文件內容
* GET /css/app.css 響應 page/css/app.css 的文件內容
* GET /images/logo.png 響應 page/images/logo.png 的文件內容
* 根據不同的文件類型,返回不同的類型
*/
// 1.引入模塊
const http = require('http')
const fs = require('fs')
const path = require('path')
// 聲明一個變量
let mimes = {
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
}
// 2.創建服務
const server = http.createServer((request, response) => {
// 獲取文件根路徑
let root = path.resolve(__dirname + '/page')
// 獲取文件路徑
let {
pathname
} = new URL(request.url, 'https://127.0.0.1:9000')
// 拼接文件路徑
let filePath = root + pathname
// 獲取文件
fs.readFile(filePath, (error, data) => {
if (error) {
// 設置字符集,解決打開文件中文亂碼的問題
response.setHeader('content-type', 'text/html;charset=utf-8')
switch(error.code) {
case 'ENOENT':
response.statusCode = 404
response.end(`<h1>404 Not Found</h1>`)
break;
case 'EPERM':
response.statusCode = 403
response.end(`<h1>403 Forbidden</h1>`)
break;
default:
response.statusCode = 500
response.end(`<h1>500 Internal </h1>`)
break;
}
return;
}
// 獲取文件后綴
let ext = path.extname(filePath).slice(1)
// 獲取對應的類型
let type = mimes[ext]
if (type) {
// 匹配到了
if (ext === 'html') {
response.setHeader('content-type', type + ';charset=utf-8')
} else {
response.setHeader('content-type', type)
}
} else {
// 沒有匹配到
// 這種返回格式可以實現下載效果
response.setHeader('content-type', 'application/octet-stream')
}
response.end(data)
})
})
// 3.啟動服務并監聽端口
server.listen(9000, () => {
console.log('服務啟動成功')
})
模塊化:將一個復雜的程序文件依據一定規則(規范)拆分成多個文件的過程稱之為模塊化。
模塊:拆分出的每個文件就是一個模塊,模塊的內部數據是私有的,不過模塊可以暴露內部數據以便其他模塊使用
模塊化項目:編碼時是按照模塊一個一個編碼的, 整個項目就是一個模塊化的項目
模塊化好處:
模塊暴露的方式有兩種:
說明:
module.exports 可以暴露任意數據和方法 不能使用 exports = value 的形式暴露,模塊內部 module 與 exports 具有隱式關系 exports = module.exports = {},require 返回的是目標模塊中 module.exports 的值
代碼示例: hello.js
const type = 'hello world'
function helloWorld() {
console.log('你好,世界....')
}
// 暴露數據、方法
module.exports = {
type,
helloWorld
}
// exports.type = type
// module.exports = helloWorld
// exports.type = type
// exports.helloWorld = helloWorld
index.js
// 引入自定應模塊
const hello = require('./hello')
// 調用模塊數據/方法
console.log(hello)
hello.helloWorld()
// hello()
在模塊中使用 require 傳入文件路徑即可引入文件 require 使用的一些注意事項:
require 導入自定義模塊的基本流程:
1、將相對路徑轉為絕對路徑,定位目標文件
2、緩存檢測
3、讀取目標文件代碼
4、裹為一個函數并執行(自執行函數),通過 arguments.callee.toString() 查看自執行函數
5、緩存模塊的值
6、返回 module.exports 的值
代碼示例:
/**
* require 導入原理 偽代碼
*/
// 1.引入模塊
const path = require('path')
const fs = require('fs')
// 2.定義一個緩存數組
let caches = []
function require(file) {
// 3.將相對路徑轉成絕對路徑
let absolutePath = path.resolve(__dirname, file)
// 4.檢測是否有緩存
if (caches[absolutePath]) {
return caches[absolutePath]
}
// 5.讀取文件的代碼
let code = fs.readFileSync(absolutePath).toString()
// 6.封裝一個函數
let module = {}
let exports = module.exports = {}
(function (exports, require, module, __fileName, __dirname) {
const test = {
name: 'hello world'
}
module.exports = test
console.log(arguments.callee.toString())
})(exports, require, module, __filename, __dirname)
// 7.緩存結果
caches[absolutePath] = module.exports
// 7.返回 module.exports 的值
return module.exports
}
包:英文單詞是 package,代表了一組特定功能的源碼集合 包管理工具:管理包的應用軟件,可以對包進行下載安裝、更新、刪除、上傳等操作。借助包管理工具,可以快速開發項目,提升開發效率。包管理工具是一個通用的概念,很多編程語言都有包管理工具,所以 掌握好包管理工具非常重要
常用的包管理工具:
npm 全稱 Node Package Manager,翻譯為中文意思是Node 的包管理工具
npm 是 Node.js 官方內置的包管理工具,是必須要掌握住的工具
常見使用命令:
命令 | 說明 |
npm -v | 查看版本號 |
npm init | 命令的作用是將文件夾初始化為一個包, 交互式創建 package.json 文件 |
npm s/search | 搜索包 |
npm install npm i | 下載安裝包 |
npm i -g | 全局安裝 |
npm i <包名@版本號> | 安裝指定版本的包 |
npm remove npm r | 局部刪除依賴 |
npm remove -g | 全局刪除依賴 |
創建一個空目錄,然后以此目錄作為工作目錄啟動命令行工具,執行 npm init. npm init 命令的作用是將文件夾初始化為一個包,交互式創建 package.json 文件。 package.json 文件是包的配置文件,每個包都必須要有, package.json 文件內容:
{
"name": "1-npm", #包的名字
"version": "1.0.0", #包的版本
"description": "", #包的描述
"main": "index.js", #包的入口文件
"scripts": { #腳本配置
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "", #作者
"license": "ISC" #開源證書
}
初始化的過程中還有一些注意事項:
name ( 包名 ) 不能使用中文、大寫,默認值是文件夾的名稱 ,所以文件夾名稱也不能使用中文和大寫 version ( 版本號 )要求 x.x.x 的形式定義, x 必須是數字,默認值是 1.0.0 ISC 證書與 MIT 證書功能上是相同的,關于開源證書擴展閱讀 http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html package.json 可以手動創建與修改 使用 npm init -y 或者 npm init --yes 極速創建 package.json
通過 npm install 和 npm i 命令安裝包
npm install uniq
npm i uniq
運行之后文件夾下會增加兩個資源
可以使用 require 導入 npm 包
// 導入 uniq 包
const uniq = require('uniq')
require 導入的基本流程:
安裝的包,也稱為依賴,依賴有生產依賴與開發依賴,二者的使用如下:
類型 | 命令 | 說明 |
生產依賴 | npm i -S uniq npm i --save uniq | -S 等效于 --save,-S 是默認選項 包信息保存在 package.json 中 dependencies 屬性 |
開發依賴 | npm i -D less npm i --save-dev less | -D 等效于 --save-dev 包信息保存在 package.json 中 devDependencies 屬性 |
項目中可能會遇到版本不匹配的情況,有時就需要安裝指定版本的包,可以使用下面的命令
npm i jquery@1.11.2
可以執行安裝選項 -g 進行全局安裝
npm i -g nodemon
全局安裝完成之后就可以在命令行的任何位置運行 nodemon 命令,該命令的作用是自動重啟 Node 應用程序
說明:
全局安裝的命令不受工作目錄位置影響 可以通過 npm root -g 可以查看全局安裝包的位置 不是所有的包都適合全局安裝,只有全局類的工具才適合,可以通過查看包的官方文檔來確定
項目中可能需要刪除某些不需要的包,可以使用下面的命令
## 局部刪除
npm remove uniq
npm r uniq
## 全局刪除
npm remove -g nodemon
可以通過配置 package.json 中的 scripts 屬性來啟動項目
{
"scripts": {
"server": "node server.js",
"start": "node index.js",
},
}
配置完成之后,執行命令啟動項目
npm run server
npm run start
不過 start 比較特別,使用時可以省略 run
npm start
npm run 有自動向上級目錄查找的特性,跟 require 函數一樣
cnpm 是一個淘寶構建的 npmjs.com 的完整鏡像,也稱為淘寶鏡像,網址:https://npmmirror.com/。
cnpm 服務部署在國內阿里云服務器上,可以提高包的下載速度。官方也提供了一個全局工具包 cnpm,操作命令與 npm 大體相同。
可以通過 npm 來安裝 cnpm 工具:
npm install -g cnpm --registry=https://registry.npmmirror.com
常用操作命令:
命令 | 說明 |
cnpm init / cnpm init | 初始化 |
cnpm i uniq cnpm i -S uniq cnpm i -D uniq cnpm i -g nodemon | 安裝包 |
cnpm i | 安裝項目依賴 |
cnpm r uniq | 刪除 |
用 npm 也可以使用淘寶鏡像,配置的方式有兩種:
執行如下命令即可完成配置:
npm config set registry https://registry.npmmirror.com/
使用 nrm 配置 npm 的鏡像地址 npm registry manager
安裝 nrm
npm i -g nrm
修改鏡像
nrm use taobao
檢查是否配置成功(選做)
npm config list
檢查 registry 地址是否為 https://registry.npmmirror.com/, 如果是則表明成功
補充說明:
建議使用第二種方式進行鏡像配置,因為后續修改起來會比較方便 雖然 cnpm 可以提高速度,但是 npm 也可以通過淘寶鏡像進行加速,所以 npm 的使用率還是高于 cnpm
yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方網址:https://yarnpkg.com/
yarn 特點:
可以使用 npm 安裝 yarn
npm i -g yarn
yarn 常用命令:
命令 | 說明 |
yarn init / yarn init -y | 初始化 |
yarn add uniq 生產依賴 yarn add less --dev 開發依賴 yarn global add nodemon 全局安裝 | 安裝包 |
yarn remove uniq 刪除項目依賴包 yarn global remove nodemon 全局刪除包 | 刪除包 |
yarn/yarn install | 安裝項目依賴 |
yarn # 不需要添加 run | 運行命令別名 |
可以通過如下命令配置淘寶鏡像
yarn config set registry https://registry.npmmirror.com/
查看 yarn 的配置項
yarn config list
npm 和 yarn 選擇:
切記:包管理工具不要混著用
可以將自己開發的工具包發布到 npm 服務上,方便自己和其他開發者使用,操作步驟如下:
1、創建文件夾,并創建文件 index.js, 在文件中聲明函數,使用 module.exports 暴露
2、npm 初始化工具包,package.json 填寫包的信息 (包的名字是唯一的)
3、注冊賬號:https://www.npmjs.com/signup
4、激活賬號(一定要激活賬號)
5、修改為官方的官方鏡像(命令行中運行 nrm use npm)
6、命令行下 npm login 填寫相關用戶信息
7、命令行下 npm publish 提交包
后續可以對自己發布的包進行更新,操作步驟如下:
1、更新包中的代碼
2、測試代碼是否可用
3、修改 package.json 中的版本號
4、發布更新命令 npm publish
執行如下命令刪除包
npm unpublish --force
刪除包需要滿足一定的條件,https://docs.npmjs.com/policies/unpublish
nvm 全稱 Node Version Manager 顧名思義它是用來管理 Node 版本的工具,方便切換不同版本的 Node.js。
nvm 的使用非常的簡單,跟 npm 的使用方法類似。
下載地址: https://github.com/coreybutler/nvm-windows/releases
常用命令:
命令 | 說明 |
nvm list available | 顯示所有可以下載的 Node.js 版本 |
nvm list | 顯示已安裝的版本 |
nvm install 18.12.1 | 安裝 18.12.1 版本的 Node.js |
nvm install latest | 安裝最新版的 Node.js |
nvm uninstall 18.12.1 | 刪除某個版本的 Node.js |
nvm use 18.12.1 | 切換 18.12.1 的 Node.js |
express 是一個基于 Node.js 平臺的極簡、靈活的 WEB 應用開發框架,官方網址:https://www.expressjs.com.cn/
簡單來說,express 是一個封裝好的工具包,封裝了很多功能,便于我們開發 web 應用(http 服務)
express 下載 express 本身是一個 npm 包,所以可以通過 npm 安裝:
npm init
npm i express
express 初體驗:
// 1.引入模塊
const express = require('express')
// 2.創建服務
const app = express()
// 3.創建路由
app.get('/hello', (req, resp) => {
resp.end('hello world')
})
// 4.啟動服務并監聽端口
app.listen(9000, () => {
console.log('服務啟動成功')
})
官方定義:路由確定了應用程序如何響應客戶端對特定端點的請求。
一個路由的組成有請求方法、路徑、回調函數組成。 express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:
app.<method>(path,callback)
代碼示例:
// 1.引入模塊
const express = require('express')
// 2.創建服務
const app = express()
// 3.創建路由
// get 請求
app.get('/hello', (req, resp) => {
resp.end('hello world')
})
// 首頁路由
app.get('/', (req, resp) => {
resp.end('home')
})
// post 請求
app.post('/login', (req, resp) => {
resp.end('login')
})
// 所有的請求方式都可以
app.all('/register', (req, resp) => {
resp.end('register')
})
// 404
app.all('*', (req, resp) => {
resp.end(`<h1>404 Not Found</h1>`)
})
// 4.啟動服務并監聽端口
app.listen(9000, () => {
console.log('服務啟動成功')
})
express 框架封裝了一些 API 來方便獲取請求報文中的數據,并且兼容原生 http 模塊的獲取方式.。 代碼示例:
// 引入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由
app.get('/hello', (req, resp) => {
// 原生方式
// console.log(req.method)
// console.log(req.url)
// console.log(req.httpVersion)
// console.log(req.headers)
// express 操作
console.log(req.path)
console.log(req.query)
// 獲取請求ip
console.log(req.ip)
resp.end('hello world')
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
express 框架也可以獲取到路由參數。路由參數指的是 URL 路徑中的參數(數據)。 代碼示例:
// 導入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由
app.get('/:id/:token', (req, resp) => {
console.log(req.params.id, req.params.token)
resp.end('hello world')
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
express 可以使用 body-parser 包處理請求體。 安裝 body-parser
npm i body-parser
代碼示例:
/**
* 需求:
* GET /login 顯示表單網頁
* POST /login 獲取表單中的『用戶名』和『密碼』
*/
// 導入模塊
const express = require('express')
// 使用 body-parser 獲取請求體數據
// 安裝 body-parser
// npm i body-parser
// 導入
const bodyParser = require('body-parser')
// 創建應用服務
const app = express()
// 解析 json 請求格式的請求體中間件
const jsonParser = bodyParser.json()
// 解析 queryString 請求格式的請求體中間件
const urlencodedParser = bodyParser.urlencoded({extended: false})
// 創建路由
app.get('/login', (req, resp) => {
resp.sendFile(__dirname + '/11-form.html')
})
app.post('/login', urlencodedParser, (req, resp) => {
// 獲取用戶名和密碼
console.log(req.body)
resp.send('登錄成功')
})
app.all('*', (req, resp) => {
resp.send(`<h1>404 Not Found</h1>`)
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
express 框架封裝了一些 API 來方便給客戶端響應數據,并且兼容原生 http 模塊的獲取方式。 代碼示例:
// 引入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由
app.get('/hello', (req, resp) => {
// 原生響應
// resp.statusCode = 404
// resp.statusMessage = 'Not Found'
// resp.setHeader('aaaa', 'bbbb')
// resp.write('hello express ')
// express 操作
// resp.status(500)
// resp.set('aaa', '123')
// 自動返回 utf8 編碼。此時無需再寫 resp.end()
// resp.send('hello express ')
// 可以鏈式調用
resp.status(500).set('xxx', 'yyy').send('你好,世界')
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
還可以設置其他響應:
// 導入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由
app.get('/hello', (req, resp) => {
// resp.end('hello world')
// 重定向
// resp.redirect('https://www.baidu.com')
// 下載響應
// resp.download(__dirname + '/singers.json')
// 返回json
// resp.json({
// username: '張三',
// age: 18
// })
// 返回文件內容
resp.sendFile(__dirname + '/2-form.html')
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
對路由進行模塊化,可以更好的管理路由。
admin.js 路由
// 導入模塊
const express = require('express')
// 創建應用服務
const router = express()
// 創建路由
router.get('/admin', (req, resp) => {
resp.send('后臺首頁')
})
router.get('/setting', (req, resp) => {
resp.send('個人設置')
})
module.exports = router
home.js 路由
// 導入模塊
const express = require('express')
// 創建應用服務
const router = express()
// 創建路由
router.get('/', (req, resp) => {
resp.send('前臺首頁')
})
router.get('/home', (req, resp) => {
resp.send('home')
})
module.exports = router
index.js 路由
// 導入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由
// 導入路由
const admin = require('./routes/admin')
const home = require('./routes/home')
app.use(admin)
app.use(home)
app.all('*', (req, resp) => {
resp.send(`<h1>404 Not Found</h1>`)
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
中間件(Middleware)本質是一個回調函數。可以像路由回調一樣訪問請求對象(request),響應對象(response)。
中間件的作用就是使用函數封裝公共操作,簡化代碼。
中間件有兩種類型:
每一個請求到達服務端之后都會執行全局中間件函數。
代碼示例:
/**
* 需求:
* 記錄每個請求的 ip 和 url
*/
// 引入模塊
const express = require('express')
const fs = require('fs')
const path = require('path')
// 創建應用服務
const app = express()
// 創建全局路由中間件
let globalMiddleware = (req, resp, next) => {
// 獲取 url 和 ip
let {ip, url} = req
// 使用追加的方式記錄到日志文件
fs.appendFileSync(path.resolve(__dirname, './access.log'), `${ip} ${url} \r\n`)
// 調用 next
next()
}
// 使用全局路由中間件
app.use(globalMiddleware)
// 創建路由
app.get('/home', (req, resp) => {
resp.send('前臺首頁')
})
app.get('/admin', (req, resp) => {
resp.send('后臺首頁')
})
app.all('*', (req, resp) => {
resp.send(`<h1>404 Not Found</h1>`)
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
如果只需要對某一些路由進行功能封裝,則就需要路由中間件。 調用格式如下:
app.get('/路徑',`中間件函數`,(request,response)=>{
});
app.get('/路徑',`中間件函數1`,`中間件函數2`,(request,response)=>{
});
代碼示例:
/**
* 需求:
* 針對 /admin /setting 的請求, 要求 URL 攜帶 code=521 參數, 如未攜帶提示『暗號錯誤』
*/
// 引入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由中間件
let routerMiddleware = (req, resp, next) => {
// 獲取 code
let { code } = req.query
if (code !== '521') {
resp.send('暗號錯誤')
return;
}
next()
}
// 創建路由
app.get('/home', (req, resp) => {
resp.send('前臺首頁')
})
app.get('/admin', routerMiddleware, (req, resp) => {
resp.send('后臺首頁')
})
app.get('/setting', routerMiddleware, (req, resp) => {
resp.send('個人設置')
})
app.all('*', (req, resp) => {
resp.send(`<h1>404 Not Found</h1>`)
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
express 內置處理靜態資源的中間件。
代碼示例:
// 導入模塊
const express = require('express')
// 創建應用服務
const app = express()
// 創建路由
app.get('/', (req, resp) => {
resp.send('前臺首頁')
})
// 靜態資源中間件設置
// '/' 路徑默認訪問 idnex.html,如果與動態資源的請求路徑一致時,誰先加載就誰先返回
app.use(express.static(__dirname + '/public'))
app.all('*', (req, resp) => {
resp.send(`<h1>404 Not Found</h1>`)
})
// 監聽端口,啟動服務
app.listen(9000, () => {
console.log('服務啟動成功')
})
注意事項:
index.html 文件為默認打開的資源 如果靜態資源與路由規則同時匹配,誰先匹配誰就響應 路由響應動態資源,靜態資源中間件響應靜態資源
模板引擎是分離用戶界面和業務數據的一種技術。
EJS 是一個高效的 Javascript 的模板引擎。
官網: https://ejs.co/
中文站:https://ejs.bootcss.com/
下載安裝 EJS
npm i ejs --save
// 安裝 ejs
// npm i ejs
// 導入模塊
const ejs = require('ejs')
const fs = require('fs')
// 聲明變量
let text = '中國'
let weather = '艷陽高照,萬里無云'
let str = fs.readFileSync(__dirname + '/1-html.html').toString()
let result = ejs.render(str, {text, weather})
console.log(result)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>我愛你<%= text %></h1></h1>
<h2><%= weather %></h1></h1>
</body>
</html>
字符串輸出
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>我愛你<%= text %></h1></h1>
<h2><%= weather %></h1></h1>
</body>
</html>
列表渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<% list.forEach( item => {%>
<li><%= item %></li>
<% })%>
</ul>
</body>
</html>
條件判斷
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<% if (isLogin) { %>
<span>歡迎回來</span>
<% } else { %>
<button>登錄</button> <button>注冊</button>
<% } %>
</body>
</html>
腳手架:可以快速創建一個應用的框架,應用框架是指在項目開發過程中常用的目錄或文件,比如:css目錄、js目錄等,以提高開發效率。
express-generator 是實現 express 框架的腳手架。
安裝 express-generator
npm i express-generator -g
初始化
express --view=ejs 項目包名
package.json 文件
{
"name": "15-express-generator",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1"
}
}
進入項目,安裝依賴
npm i
啟動項目
npm start
瀏覽器訪問,端口默認是:3000 localhost:3000
MongoDB 是一個基于分布式文件存儲的數據庫。
官方地址:https://www.mongodb.com/
下載地址: https://www.mongodb.com/try/download/community
MongoDB 操作語法與 JavaScript 類似,容易上手,學習成本低。
MongoDB 中有三個重要概念需要掌握:
可以通過 JSON 文件來理解 MongoDB 中的概念: 一個 JSON 文件好比是一個數據庫,一個 Mongodb 服務下可以有 N 個數據庫;JSON 文件中的一級屬性的數組值好比是集合;數組中的對象好比是文檔對象中的屬性,有時也稱之為字段。
一般情況下,一個項目使用一個數據庫,一個集合會存儲同一種類型的數據。
顯示所有的數據庫
show dbs
切換到指定的數據庫,如果數據庫不存在會自動創建數據庫
use 數據庫名
顯示當前所在的數據庫
db
刪除當前數據庫
use 庫名
db.dropDatabase()
創建集合
db.createCollection('集合名稱')
顯示當前數據庫中的所有集合
show collections
刪除某個集合
db.集合名.drop()
重命名集合
db.集合名.renameCollection('newName')
插入文檔
db.集合名.insert(文檔對象)
查詢文檔
db.集合名.find(查詢條件)
_id 是 MongoDB 自動生成的唯一編號,用來唯一標識文檔
更新文檔
db.集合名.update(查詢條件,新的文檔)
db.集合名.update({name:'張三'},{$set:{age:19}})
刪除文檔
db.集合名.remove(查詢條件)
Mongoose 是一個 對象文檔模型庫,可以方便使用代碼操作 MongoDB 數據庫。
官網 http://www.mongoosejs.net/
代碼示例:
// 1.導入 mongoose
const mongoose = require('mongoose')
// 設置 strictQuery 為 true
mongoose.set('strictQuery', true)
// 2.連接 mongodb 服務
mongoose.connect('mongodb://127.0.0.1:27017/test')
// 3.設置連接成功的回調函數
mongoose.connection.once('open', () => {
// 創建新文檔
// 1、創建文檔的結構模型
let bookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
});
// 2、創建文檔模型對象,用于操作文檔
// book 就是集合名,mongoose 會默認加上 s,在mongodb數據庫中顯示的是 books
let bookModel = mongoose.model('book', bookSchema);
// 3、操作文檔-新增
bookModel.create({
name: "海邊的卡夫卡",
author: '樹上春樹',
price: 49
}, (err, data) => {
// 判斷是否有錯誤
if (err) {
console.log('文檔新增失敗', err)
return;
}
console.log('文檔新增成功', data)
});
console.log('連接成功')
})
// 4.設置連接失敗的回調函數
mongoose.connection.on('error', () => {
console.log('連接失敗')
})
// 5.設置連接關閉的回調函數
mongoose.connection.on('close', () => {
console.log('連接關閉')
})
文檔結構可選的常用字段類型列表
類型 | 描述 |
String | 字符串 |
Number | 數字 |
Boolean | 布爾值 |
Array | 數組,也可以使用 [] 來標識 |
Date | 日期 |
Buffer | Buffer 對象 |
Mixed | 任意類型,需要使用 mongoose.Schema.Types.Mixed 指定 |
ObjectId | 對象 ID,需要使用 mongoose.Schema.Types.ObjectId 指定 |
Decimal128 | 高精度數字,需要使用 mongoose.Schema.Types.Decimal128 指定 |
Mongoose 有一些內建驗證器,可以對字段值進行驗證
必填項
name: {
type: String,
// 設置是否必填
// name: ValidatorError: Path `name` is required.
required: true
}
默認值
author: {
type: String,
// 設置默認值
default: '匿名'
}
枚舉值
tages: {
type: String,
// 枚舉
// tages: ValidatorError: `架空` is not a valid enum value for path `tages`.
enum: ['言情', '歷史', '勵志']
}
唯一值
name: {
type: String,
// 是否唯一
unique: true
}
unique 需要重建集合才能有效果。
永遠不要相信用戶的輸入!
插入一條
bookModel.create({
name: "海邊的卡夫卡",
author: '樹上春樹',
price: 49
}, (err, data) => {
// 判斷是否有錯誤
if (err) {
console.log('文檔新增失敗', err)
return;
}
console.log('文檔新增成功', data)
});
批量插入
bookModel.insertMany([{
name: '西游記',
author: '吳承恩',
price: 19.9,
}, {
name: '紅樓夢',
author: '曹雪芹',
price: 29.9,
}, {
name: '三國演義',
author: '羅貫中',
price: 25.9,
}, {
name: '水滸傳',
author: '施耐庵',
price: 20.9,
}, {
name: '活著',
author: '余華',
price: 19.9,
}, {
name: '狂飆',
author: '徐紀周',
price: 68,
}, {
name: '大魏能臣',
author: '黑男爵',
price: 9.9,
},
{
name: '知北游',
author: '洛水',
price: 59,
}, {
name: '道君',
author: '躍千愁',
price: 59,
}, {
name: '七煞碑',
author: '游泳的貓',
price: 29,
}, {
name: '獨游',
author: '酒精過敏',
price: 15,
}, {
name: '大潑猴',
author: '甲魚不是龜',
price: 26,
},
{
name: '黑暗王者',
author: '古羲',
price: 39,
},
{
name: '不二大道',
author: '文刀手予',
price: 89,
},
{
name: '大潑猴',
author: '甲魚不是龜',
price: 59,
},
{
name: '長安的荔枝',
author: '馬伯庸',
price: 45,
},
{
name: '命運',
author: '蔡崇達',
price: 59.8,
},
{
name: '如雪如山',
author: '張天翼',
price: 58,
},
{
name: '三體',
author: '劉慈欣',
price: 23,
},
{
name: '秋園',
author: '楊本芬',
price: 38,
},
{
name: '百年孤獨',
author: '范曄',
price: 39.5,
},
{
name: '在細雨中呼喊',
author: '余華',
price: 25,
},
], (error, data) => {
if (error) {
console.log('批量新增失敗', error)
return
}
console.log('批量新增成功')
})
刪除一條數據
bookModel.deleteOne({name: '西游記'}, (error, data) => {
if (error) {
console.log('刪除文檔失敗', data)
return
}
console.log('刪除文檔成功', data)
})
批量刪除
bookModel.deleteMany({
name: '海邊的卡夫卡'
}, (error, data) => {
if (error) {
console.log('批量刪除失敗', error)
return
}
console.log('批量刪除成功', data)
})
更新一條數據
bookModel.updateOne({
name: '海邊的卡夫卡'
}, {
name: '西游記',
author: '吳承恩',
price: 59
}, (error, data) => {
if (error) {
console.log('更新文檔失敗', error)
return;
}
console.log('更新文檔成功', data)
})
批量更新數據
bookModel.updateMany({
name: '海邊的卡夫卡'
}, {
price: 89
}, (error, data) => {
if (error) {
console.log('批量更新文檔失敗', error)
return
}
console.log('批量更新文檔成功', data)
})
查詢一條數據
bookModel.findOne({name: '西游記'}, (error, data) => {
if (error) {
console.log('讀取單個文檔錯誤', error)
return
}
console.log('讀取單個文檔成功', data)
})
批量查詢數據
bookModel.find({name: '西游記'}, (error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return;
}
console.log('文檔讀取成功', data)
})
在 MongoDB 不能 >、<、>=、<=、!== 等運算符,需要使用替代符號:
> 使用 $gt < 使用 $lt >= 使用 $gte <= 使用 $lte !== 使用 $ne
// 獲取價格 小于 20 的圖書
bookModel.find({price: {$lt: 20}}, (error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return;
}
console.log('文檔讀取成功', data)
})
邏輯運算 $or 邏輯或的情況
//曹雪芹 或者 余華的書
bookModel.find({
$or: [
{
author: '曹雪芹'
},
{
author: '余華'
}
]
}, (error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return
}
console.log('文檔讀取成功', data)
})
$and 邏輯與的情況
//價格大于等于 30 且小于 70
bookModel.find({
$and: [
{
price: {
$gte: 30
}
}, {
price: {
$lt: 70
}
}
]
}, (error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return;
}
console.log('文檔讀取成功', data)
})
正則匹配 條件中可以直接使用 JS 的正則語法,通過正則可以進行模糊查詢
//正則表達式, 搜索書籍名稱中帶有 `三` 的圖書
let queryCondition = '三'
bookModel.find({name: /三/}, (error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return;
}
console.log('文檔讀取成功', data)
})
bookModel.find({name: new RegExp(queryCondition)}, (error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return;
}
console.log('文檔讀取成功', data)
})
字段篩選
// 只查詢 書名 作者
// 1:查詢 0:不查詢
bookModel.find().select({name: 1, author: 1, _id: 0}).exec((error, data) => {
if (error) {
console.log('文檔讀取錯誤', error)
return
}
console.log('文檔讀取成功', data)
})
數據排序
// 對數據進行排序
// 1:升序 -1:降序
bookModel.find({author: '余華'}).select({name: 1, price: 1}).sort({price: -1}).exec((error, data) => {
if (error) {
console.log('文件讀取錯誤', error)
return
}
console.log(data)
})
數據截取
// 數據列表集合截取
// 可用于分頁
bookModel.find()
.select({name: 1, author: 1, _id: 0})
.sort({price: -1})
.skip(2)
.limit(3)
.exec((error, data) => {
if (error) {
console.log('文件讀取錯誤', error)
return
}
console.log(data)
})
RESTful API 是一種特殊風格的接口,主要特點有如下幾個:
規則示例:
操作 | 請求類型 | URL |
新增歌曲 | POST | /song |
刪除歌曲 | DELETE | /song/10 |
修改歌曲 | PUT | /song/10 |
修改歌曲 | PATCH | /song/10 |
獲取所有歌曲 | GET | /song |
獲取單個歌曲 | GET | /song/10 |
json-server 本身是一個 JS 編寫的工具包,可以快速搭建 RESTful API 服務。
官方地址: https://github.com/typicode/json-server
操作步驟: 全局安裝 json-server
npm i -g json-server
創建 JSON 文件(db.json),編寫基本結構
{
"song": [
{ "id": 1, "name": "干杯", "singer": "五月天" },
{ "id": 2, "name": "當", "singer": "動力火車" },
{ "id": 3, "name": "不能說的秘密", "singer": "周杰倫" }
]
}
以 JSON 文件所在文件夾作為工作目錄,執行如下命令
json-server --watch db.json
默認監聽端口為 3000
所謂會話控制就是對會話進行控制。
HTTP 是一種無狀態的協議,它沒有辦法區分多次的請求是否來自于同一個客戶端,無法區分用戶而產品中又大量存在的這樣的需求,所以我們需要通過會話控制來解決該問題。
常見的會話控制技術有三種:
cookie 是 HTTP 服務器發送到用戶瀏覽器并保存在本地的一小塊數據,保存在瀏覽器端的一小塊數據且是按照域名劃分保存的。
express 中可以使用 cookie-parser 來進行對 cookie 的操作。
示例代碼:
// 導入express
const express = require('express')
const cookieParser = require('cookie-parser')
// 創建應用對象
const app = express()
app.use(cookieParser())
// 創建路由
app.get('/', (req, resp) => {
resp.send('home')
})
// 設置cookie
app.get('/set-cookie', (req, resp) => {
// 在瀏覽器關閉時會銷毀
// resp.cookie('name', 'zhangsan')
// 設置 cookie 的過期時間。設置一分鐘以后過期
resp.cookie('name', 'lisi', {maxAge: 60 * 1000})
resp.send('hello world')
})
// 獲取 cookie
app.get('/get-cookie', (req, resp) => {
console.log(req.cookies)
resp.send(`歡迎您${req.cookies.name}`)
})
// 刪除 cookie
app.get('/remove-cookie', (req, resp) => {
resp.clearCookie('name')
resp.send('刪除cookie成功')
})
// 監聽端口,啟動服務
app.listen(3000, () => {
console.log('服務啟動成功')
})
不同瀏覽器中的 cookie 是相互獨立的,不共享。
session 是保存在服務器端的一塊兒數據 ,保存當前訪問用戶的相關信息。
express 中可以使用 express-session 對 session 進行操作。
示例代碼:
// 引入模塊
const express = require('express')
const session = require('express-session')
const MongoStore = require('connect-mongo')
// 創建應用服務
const app = express()
// 設置 session 的中間件
app.use(session({
// 設置 cookie 的 name,默認值是:connect.sid
name: 'sid',
// 參與加密的字符串(又稱簽名) 加鹽
secret: '123456',
// 是否為每次請求都設置一個cookie用來存儲session的id
saveUninitialized: false,
// 是否在每次請求時重新保存session 20 分鐘 4:00 4:20
resave: true,
store: MongoStore.create({
//數據庫的連接配置
mongoUrl: 'mongodb://127.0.0.1:27017/test'
}),
cookie: {
// 開啟后前端無法通過 JS 操作
httpOnly: true,
// 這一條 是控制 sessionID 的過期時間的!!! 5分鐘過期
maxAge: 5 * 60 * 1000
}
}))
// 創建路由
app.get('/', (req, resp) => {
resp.send('home')
})
// 登錄時設置 session
app.get('/login', (req, resp) => {
// 校驗一下用戶名密碼是否正確
if (req.query.username === 'admin' && req.query.password === 'admin') {
// 設置 session 信息
req.session.username = req.query.username
req.session.uid = '258aefccc'
resp.send('登錄成功')
} else {
resp.send('登錄失敗')
}
})
// 獲取 session
app.get('/cart', (req, resp) => {
// 檢測 session 是否存在用戶信息
if (req.session.username) {
resp.send(`歡迎您${req.session.username}`)
} else {
resp.send('請先登錄')
}
})
// 退出登錄刪除 session
app.get('/logout', (req, resp) => {
req.session.destroy(() => {
resp.send('成功退出登錄')
})
})
// 監聽端口,啟動服務
app.listen(3000)
session 和 cookie 的區別 cookie 和 session 的區別主要有如下幾點:
token 是服務端生成并返回給 HTTP 客戶端的一串加密字符串, token 中保存著用戶信息。
token 的特點:
JWT(JSON Web Token)是目前最流行的跨域認證解決方案,可用于基于 token 的身份驗證 JWT 使 token 的生成與校驗更規范,可以使用 jsonwebtoken 包 來操作 token.
代碼操作:
nodejs作為一個優秀的異步IO框架,其本身就是用來作為http web服務器使用的,nodejs中的http模塊,提供了很多非常有用的http相關的功能。
雖然nodejs已經帶有http的處理模塊,但是對于現代web應用程序來說,這或許還不太夠,于是我們有了express框架,來對nodejs的內容進行擴展。
今天我們將會介紹一下使用nodejs和express來開發web應用程序的區別。
nodejs提供了http模塊,我們可以很方便的使用http模塊來創建一個web服務:
const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('welcome to www.flydean.com\n')
})
server.listen(port, hostname, () => {
console.log(`please visit http://{hostname}:{port}/`)
})
上面創建的http服務監聽在3000端口。我們通過使用createServer方法來創建這個http服務。
該方法接受一個callback函數,函數的兩個參數分別是 req (http.IncomingMessage 對象)和一個res(http.ServerResponse 對像)。
在上面的例子中,我們在response中設置了header和body值,并且以一個end方法來結束response。
我們創建好http web服務之后,一般情況下是從web瀏覽器端進行訪問和調用。但是我們有時候也需要從nodejs后端服務中調用第三方應用的http接口,下面的例子將會展示如何使用nodejs來調用http服務。
先看一個最簡單的get請求:
const http = require('http')
const options = {
hostname: 'www.flydean.com',
port: 80,
path: '/',
method: 'GET'
}
const req = http.request(options, res => {
console.log(`status code: ${res.statusCode}`)
res.on('data', d => {
console.log(d);
})
})
req.on('error', error => {
console.error(error)
})
req.end()
上面代碼我們使用了http.request來創建一個request,并且傳入了我們自帶的options參數。
我們通過res的回調事件來進行相應的處理。
再看一個簡單的post請求:
const http = require('http')
const data = JSON.stringify({
name: 'flydean'
})
const options = {
hostname: 'www.flydean.com',
port: 80,
path: '/',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
}
const req = http.request(options, res => {
console.log(`status code: ${res.statusCode}`)
res.on('data', d => {
console.log(d);
})
})
req.on('error', error => {
console.error(error)
})
req.write(data)
req.end()
post和get相似,不同的是options中的method不一樣,同時put可以有多種請求類型,所以我們需要在headers中指定。
同樣的,PUT 和 DELETE 也可以使用同樣的方式來調用。
直接使用nodejs底層的http.request有點復雜,我們需要自己構建options,如果使用第三方庫,比如axios可以讓post請求變得更加簡單:
const axios = require('axios')
axios
.post('http://www.flydean.com', {
name: 'flydean'
})
.then(res => {
console.log(`status code: ${res.statusCode}`)
console.log(res)
})
.catch(error => {
console.error(error)
})
上面的例子中,我們直接使用axios的post請求,并將請求結果封存成了promise,然后通過then和catch來進行相應數據的處理。非常的方便。
在上面的例子中,我們通過監聽req的data事件來輸出http請求的正文:
res.on('data', d => {
console.log(d);
})
})
這樣做其實是有問題的,并不一定能夠獲得完整的http請求的正文。
因為res的on data事件是在服務器獲得http請求頭的時候觸發的,這個時候請求的正文可能還沒有傳輸完成,換句話說,請求回調中的request是一個流對象。
我們需要這樣處理:
const server = http.createServer((req, res) => {
let data = []
req.on('data', chunk => {
data.push(chunk)
})
req.on('end', () => {
console.log(JSON.parse(data));
})
})
當每次觸發data事件的時候,我們將接受到的值push到一個數組里面,等所有的值都接收完畢,觸發end事件的時候,再統一進行輸出。
這樣處理顯然有點麻煩。
我們介紹一個在express框架中的簡單方法,使用 body-parser 模塊:
const bodyParser = require('body-parser')
app.use(
bodyParser.urlencoded({
extended: true
})
)
app.use(bodyParser.json())
app.post('/', (req, res) => {
console.log(req.body)
})
上面的例子中,body-parser對req進行了封裝,我們只用關注與最后的結果即可。
express是什么呢?
express是基于 Node.js 平臺,快速、開放、極簡的 web 開發框架。它提供一系列強大的特性,幫助你創建各種 Web 和移動設備應用。
豐富的 HTTP 快捷方法和任意排列組合的 Connect 中間件,讓你創建健壯、友好的 API 變得既快速又簡單。
Express 不對 Node.js 已有的特性進行二次抽象,我們只是在它之上擴展了 Web 應用所需的基本功能。
我們看一下怎么使用Express來搭建一個helloworld:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
簡單的使用app.listen即可搭建好一個http web服務。
有了web服務,我們需要對不同的請求路徑和請求方式進行不同的處理,這時候就需要使用到了express路由功能:
// 對網站首頁的訪問返回 "Hello World!" 字樣
app.get('/', function (req, res) {
res.send('Hello World!');});
// 網站首頁接受 POST 請求
app.post('/', function (req, res) {
res.send('Got a POST request');});
// /user 節點接受 PUT 請求
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user');});
// /user 節點接受 DELETE 請求
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user');});
更高級一點的,我們還可以在請求路徑中做路由匹配:
// 匹配 acd 和 abcd
app.get('/ab?cd', function(req, res) {
res.send('ab?cd');});
// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', function(req, res) {
res.send('ab+cd');
});
// 匹配 abcd、abxcd、abRABDOMcd、ab123cd等
app.get('/ab*cd', function(req, res) {
res.send('ab*cd');
});
// 匹配 /abe 和 /abcde
app.get('/ab(cd)?e', function(req, res) {
res.send('ab(cd)?e');});
// 匹配任何路徑中含有 a 的路徑:
app.get(/a/, function(req, res) {
res.send('/a/');
});
// 匹配 butterfly、dragonfly,不匹配 butterflyman、dragonfly man等
app.get(/.*fly/, function(req, res) {
res.send('/.*fly/');
});
有時候,一個請求可能有多個處理器,express提供了路由句柄(中間件)的功能,我們可自由組合處理程序。
注意,在路由句柄中,我們需要調用next方法,來觸發下一個路由方法。
var cb0 = function (req, res, next) {
console.log('CB0');
next();}
var cb1 = function (req, res, next) {
console.log('CB1');
next();}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from D!');
});
上面的請求會經過cb0,cb1和自定義的兩個function,最終結束。
express提供了很多響應方法API,可以方便我們的代碼編寫:
方法描述res.download()提示下載文件。res.end()終結響應處理流程。res.json()發送一個 JSON 格式的響應。res.jsonp()發送一個支持 JSONP 的 JSON 格式的響應。res.redirect()重定向請求。res.render()渲染視圖模板。res.send()發送各種類型的響應。res.sendFile以八位字節流的形式發送文件。res.sendStatus()設置響應狀態代碼,并將其以字符串形式作為響應體的一部分發送。
通常來說,靜態資源是不需要服務端進行處理的,在express中,可以使用express.static來指定靜態資源的路徑:
app.use(express.static('public'));
現在,public 目錄下面的文件就可以訪問了。
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png
http://localhost:3000/hello.html
//多個靜態資源目錄
app.use(express.static('public'));
app.use(express.static('files'));
//靜態前綴
app.use('/static', express.static('public'));
http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css
web應用當然需要html文件,express中可以使用多種模板語言,讓編寫html頁面更加容易。如果想要使用模板引擎。我們可以使用下面的步驟:
html
head
title!= title
body
h1!= message
//配置route 規則
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
nodejs和express是非常方便的http web服務框架,希望大家能夠喜歡。
本文作者:flydean程序那些事
本文鏈接:http://www.flydean.com/nodejs-http-express/
本文來源:flydean的博客
歡迎關注我的公眾號:「程序那些事」最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。