Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境,使用了一個事件驅動、非阻塞式 I/O 模型,讓 JavaScript 運行在服務端的開發平臺。 ...
0. 基礎概念
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
1. Buffer
1.1 概念
Buffer
是一個類似於數組的對象,用於表示固定長度的位元組序列。Buffer
本質是一段記憶體空間,專門用來處理二進位數據。
1.2 特點
Buffer
大小固定且無法調整Buffer
性能較好,可以直接對電腦記憶體進行操作- 每個元素的大小為 1 位元組(byte)
1.3 使用
1.3.1 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]);
1.3.2 Buffer
與字元串的轉化
可以藉助 toString
方法將 Buffer
轉為字元串
// buffer 與字元串的轉換
const buffer = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
// 預設使用 UTF-8 的編碼格式
console.log(buffer.toString())
註意:
toString
預設是按照utf-8
編碼方式進行轉換的。
1.3.3 Buffer
的操作
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個位元組
2. fs
模塊
fs
全稱為file system
,稱之為文件系統,是Node.js
中的內置模塊,可以對電腦中的磁碟進行操作。
2.1 文件寫入
文件寫入就是將數據保存到文件中,有如下的 API
方法 | 說明 |
---|---|
writeFile | 非同步寫入 |
writeFileSync | 同步寫入 |
appendFile/appendFileSync | 追加寫入 |
createWriteStream | 流式寫入 |
2.1.1 非同步寫入
語法: fs.writeFile(file, data, [options], callback)
參數說明:
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('文件寫入成功')
});
2.1.2 同步寫入
語法: fs.writeFileSync(file, data, [options])
參數說明:
file
:文件名data
:待寫入的數據options
:選項設置 (可選)
返回值:undefined
代碼示例:
// 1.導入 fs 模塊
const fs = require('fs')
// 2.寫入文件
// 同步寫入,沒有回調函數
fs.writeFileSync('./座右銘.txt', '封狼居胥,禪於姑衍,飲馬瀚海,燕石勒然')
Node.js
中的磁碟操作是由其他線程完成的,結果的處理有兩種模式:
- 同步處理
JavaScript
主線程會等待其他線程的執行結果,然後再繼續執行主線程的代碼,效率較低- 非同步處理
JavaScript
主線程不會等待其他線程的執行結果,直接執行後續的主線程代碼,效率較好
2.1.3 追加寫入
appendFile
作用是在文件尾部追加內容,appendFile
語法與 writeFile
語法完全相同
語法:
- fs.appendFile(file, data, [options], callback)
- fs.appendFileSync(file, data, [options])
返回值: 二者都為 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')
2.1.4 流式寫入
語法: fs.createWriteStream(path, [options])
參數說明:
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
適合於寫入頻率較低的場景。
2.2 文件讀取
文件讀取顧名思義,就是通過程式從文件中取出其中的數據,有如下幾種 API
:
方法 | 說明 |
---|---|
readFile | 非同步讀取 |
readFileSync | 同步讀取 |
createReadStream | 流式讀取 |
2.2.1 非同步讀取
語法: fs.readFile(path, [options], callback)
參數說明:
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())
})
2.2.2 同步讀取
語法: fs.readFileSync(path, [options])
參數說明:
path
:文件路徑options
:選項配置
返回值: string
| Buffer
代碼示例:
// 1.引入 fs 模塊
const fs = require('fs')
// 2.同步讀取
let data = fs.readFileSync('./座右銘.txt');
console.log(data.toString())
2.2.3 流式讀取
語法: fs.createReadStream(path, [options])
參數說明:
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('文件讀取完成')
})
2.3 文件移動與重命名
在 Node.js
中,可以使用 rename
或 renameSync
來移動或重命名文件或文件夾
語法:
fs.rename(oldPath, newPath, callback)
fs.renameSync(oldPath, newPath)
參數說明:
oldPath
:文件當前的路徑newPath
:文件新的路徑callback
:操作後的回調
代碼示例:
// 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('操作成功')
})
2.4 文件刪除
在 Node.js
中,可以使用 unlink
或 unlinkSync
或 rm
或 rmSync
來刪除文件
語法:
fs.unlink(path, callback)
fs.unlinkSync(path)
參數說明:
path
:文件路徑callback
:操作後的回調
代碼示例:
// 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('文件刪除成功')
})
2.5 文件夾操作
在 Node.js
中可以通過如下 API
對文件夾進行創建、讀取、刪除等操作
方法 | 說明 |
---|---|
mkdir / mkdirSync | 創建文件夾 |
readdir / readdirSync | 讀取文件夾 |
rmdir / rmdirSync | 刪除文件夾 |
2.5.1 創建文件夾
在 Node.js
中,可以使用 mkdir
或 mkdirSync
來創建文件夾
語法:
fs.mkdir(path, [options], callback)
fs.mkdirSync(path, [options])
參數說明:
path
:文件夾路徑options
:選項配置(可選)callback
:操作後的回調
示例代碼:
// 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});
2.5.2 讀取文件夾
在 Node.js
中,可以使用 readdir
或 readdirSync
來讀取文件夾
語法:
fs.readdir(path, [options], callback)
fs.readdirSync(path, [options])
參數說明:
path
:文件夾路徑options
:選項配置(可選)callback
:操作後的回調
示例代碼:
// 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);
2.5.3 刪除文件夾
在 Node.js
中,可以使用 rmdir
或 rmdirSync
來刪除文件夾
語法:
fs.rmdir(path, [options], callback)
fs.rmdirSync(path, [options])
參數說明:path
:文件夾路徑options
:選項配置(可選)callback
:操作後的回調
示例代碼:
// 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})
2.6 查看資源狀態
在 Node.js
中,可以使用 stat
或 statSync
來查看資源的詳細信息
語法:
fs.stat(path, [options], callback)
fs.statSync(path, [options])
參數說明:
path
:文件夾路徑options
:選項配置(可選)callback
:操作後的回調
示例代碼:
// 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())
結果值對象結構:
size
:文件體積birthtime
:創建時間mtime
:最後修改時間isFile
:檢測是否為文件isDirectory
:檢測是否為文件夾
2.7 相對路徑問題
fs
模塊對資源進行操作時,路徑的寫法有兩種:
- 相對路徑
./座右銘.txt
:當前目錄下的座右銘.txt座右銘.txt
:等效於上面的寫法../座右銘.txt
:當前目錄的上一級目錄中的座右銘.txt
- 絕對路徑
D:/Program Files
:windows
系統下的絕對路徑/usr/bin
:Linux
系統下的絕對路徑
相對路徑中所謂的當前目錄,指的是命令行的工作目錄,而並非是文件的所在目錄。所以當命令行的工作目錄與文件所在目錄不一致時,會出現一些
BUG
。
2.8 __dirname
__dirname
與 require
類似,都是 Node.js
環境中的 '全局' 變數
__dirname
保存著當前文件所在目錄的絕對路徑,可以使用 __dirname
與文件名拼接成絕對路徑
代碼示例:
let data = fs.readFileSync(__dirname + '/data.txt');
console.log(data);
使用
fs
模塊的時候,儘量使用__dirname
將路徑轉化為絕對路徑,這樣可以避免相對路徑產生的Bug
2.9 練習
實現文件複製的功能
代碼示例:
/**
* 需求:
* 複製【座右銘.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)
3. path
模塊
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))
4. http
模塊
4.1 創建 http
服務
4.1.1 操作步驟
代碼示例:
// 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
請求的時候,就會執行
4.1.2 測試
瀏覽器請求對應埠
http://127.0.0.1:9000
4.1.3 註意事項
- 命令行
ctrl + c
停止服務 - 當服務啟動後,更新代碼必須重啟服務才能生效
- 響應內容中文亂碼的解決辦法
response.setHeader('content-type', 'text/html;charset=utf-8')
- 埠號被占用
- 關閉當前正在運行監聽埠的服務
- 修改其他埠號
- 如果埠被其他程式占用,可以使用資源監視器找到占用埠的程式,然後使用任務管理器關閉對應的程式
4.2 獲取 http
請求報文
想要獲取請求的數據,需要通過 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){}) request.on('end', function(){}); |
請求體 |
註意事項:
request.url
只能獲取路徑以及查詢字元串,無法獲取URL
中的功能變數名稱以及協議的內容request.headers
將請求信息轉化成一個對象,並將屬性名都轉化成了『小寫』- 關於路徑:如果訪問網站的時候,只填寫了
IP
地址或者是功能變數名稱信息,此時請求的路徑為『/』 - 關於
favicon.ico
:這個請求是屬於瀏覽器自動發送的請求
代碼示例:
// 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('服務啟動成功')
})
4.3 設置 http
響應報文
方法 | 說明 |
---|---|
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('服務啟動成功')
})
4.4 練習
4.4.1 http
請求練習
需求:
當請求方式為 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.4.1 http
響應練習
需求:
回一個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('服務啟動成功')
})
4.5 靜態資源服務
靜態資源是指內容長時間不發生改變的資源,例如圖片、視頻、
CSS
文件、JS
文件、HTML
文件、字體文件等。
動態資源是指內容經常更新的資源,例如百度首頁、網易首頁、京東搜索列表頁面等。
4.5.1 網頁中的 URL
網頁中的
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 |
4.5.2 設置資源類型(mime
類型)
媒體類型(通常稱為
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('服務啟動成功')
})
4.5.3 GET
和 POST
請求的區別
GET
和POST
是http
協議請求的兩種方式。GET
主要用來獲取數據,POST
主要用來提交數據GET
帶參數請求是將參數綴到URL
之後,在地址欄中輸入URL
訪問網站就是GET
請求,POST
帶參數請求是將參數放到請求體中POST
請求相對GET
安全一些,因為在瀏覽器中參數會暴露在地址欄GET
請求大小有限制,一般為2K
,而POST
請求則沒有大小限制
5. 模塊化
5.1 基礎概念
模塊化:將一個複雜的程式文件依據一定規則(規範)拆分成多個文件的過程稱之為模塊化。
模塊:拆分出的每個文件就是一個模塊,模塊的內部數據是私有的,不過模塊可以暴露內部數據以便其他模塊使用
模塊化項目:編碼時是按照模塊一個一個編碼的, 整個項目就是一個模塊化的項目
模塊化好處:
- 防止命名衝突
- 高復用性
- 高維護性
5.2 模塊暴露
模塊暴露的方式有兩種:
module.exports = value
exports.name = value
說明:
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()
5.3 模塊導入
在模塊中使用 require
傳入文件路徑即可引入文件
require
使用的一些註意事項:
- 對於自己創建的模塊,導入時路徑建議寫相對路徑,且不能省略
./
和../
js
和json
文件導入時可以不用寫尾碼,c/c++
編寫的node
擴展文件也可以不寫尾碼,但是一般用不到- 如果導入其他類型的文件,會以
js
文件進行處理 - 如果導入的路徑是個文件夾,則會首先檢測該文件夾下
package.json
文件中main
屬性對應的文件,如果存在則導入,反之如果文件不存在會報錯。如果main
屬性不存在,或者package.json
不存在,則會嘗試導入文件夾下的index.js
和index.json
,如果還是沒找到,就會報錯 - 導入
node.js
內置模塊時,直接require
模塊的名字即可,無需加./
和../
5.4 模塊導入流程
require
導入自定義模塊的基本流程:
- 將相對路徑轉為絕對路徑,定位目標文件
- 緩存檢測
- 讀取目標文件代碼
- 包裹為一個函數並執行(自執行函數),通過
arguments.callee.toString()
查看自執行函數 - 緩存模塊的值
- 返回
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
}
6. 包管理工具
6.1 基礎概念
包:英文單詞是 package
,代表了一組特定功能的源碼集合
包管理工具:管理包的應用軟體,可以對包進行下載安裝、更新、刪除、上傳等操作。藉助包管理工具,可以快速開發項目,提升開發效率。包管理工具是一個通用的概念,很多編程語言都有包管理工具,所以 掌握好包管理工具非常重要
常用的包管理工具:
npm
cnpm
yarn
pnpm
6.2 npm
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 |
全局刪除依賴 |
6.2.1 初始化
創建一個空目錄,然後以此目錄作為工作目錄啟動命令行工具,執行 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.htmlpackage.json
可以手動創建與修改- 使用
npm init -y
或者npm init --yes
極速創建package.json
6.2.2 包管理
通過 npm install
和 npm i
命令安裝包
npm install uniq
npm i uniq
運行之後文件夾下會增加兩個資源
node_modules
文件夾存放下載的包,node_modules
文件夾大多數情況都不會存入版本庫(git
、svn
等)package-lock.json
包的鎖文件,用來鎖定包的版本
可以使用 require
導入 npm
包
// 導入 uniq 包
const uniq = require('uniq')
require
導入的基本流程:
- 在當前文件夾下
node_modules
中尋找同名的文件夾 - 在上級目錄中下的
node_modules
中尋找同名的文件夾,直至找到磁碟根目錄
安裝的包,也稱為依賴,依賴有生產依賴與開發依賴,二者的使用如下:
類型 | 命令 | 說明 |
---|---|---|
生產依賴 | 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 [email protected]
可以執行安裝選項 -g
進行全局安裝
npm i -g nodemon
全局安裝完成之後就可以在命令行的任何位置運行 nodemon
命令,該命令的作用是自動重啟 Node 應用程式
說明:
- 全局安裝的命令不受工作目錄位置影響
- 可以通過
npm root -g
可以查看全局安裝包的位置- 不是所有的包都適合全局安裝,只有全局類的工具才適合,可以通過查看包的官方文檔來確定
項目中可能需要刪除某些不需要的包,可以使用下麵的命令
## 局部刪除
npm remove uniq
npm r uniq
## 全局刪除
npm remove -g nodemon
6.2.3 啟動項目
可以通過配置 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
函數一樣
6.3 cnpm
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
6.4 yarn
yarn
是由 Facebook
在 2016 年推出的新的 Javascript
包管理工具,官方網址:https://yarnpkg.com/
yarn
特點:
- 速度超快:
yarn
緩存了每個下載過的包,所以再次使用時無需重覆下載。同時利用並行下載以最大化資源利用率,因此安裝速度更快。 - 超級安全:在執行代碼之前,
yarn
會通過演算法校驗每個安裝包的完整性 - 超級可靠:使用詳細、簡潔的鎖文件格式和明確的安裝演算法,
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
的鎖文件為package-lock.json
yarn
的鎖文件為yarn.lock
切記:包管理工具不要混著用
6.5 管理髮布包
可以將自己開發的工具包發佈到 npm
服務上,方便自己和其他開發者使用,操作步驟如下:
- 創建文件夾,並創建文件
index.js
, 在文件中聲明函數,使用module.exports
暴露 npm
初始化工具包,package.json
填寫包的信息 (包的名字是唯一的)- 註冊賬號:https://www.npmjs.com/signup
- 激活賬號(一定要激活賬號)
- 修改為官方的官方鏡像(命令行中運行
nrm use npm
) - 命令行下
npm login
填寫相關用戶信息 - 命令行下
npm publish
提