JS 是腳本語言,腳本語言都須要一個解析器才能運行。對於寫在 HTML 頁面裏的 JS,瀏覽器充當瞭解析器的角色。而對於須要獨立運行的 JS,NodeJS 就是一個解析器。html
解析器須要運行引擎才能對 JavaScript 進行解析,Node.js 採用了 V8 引擎,Google 開源的 JavaScript 引擎。前端
因此,Node.js 就是一個基於 Chrome V8 引擎的 JavaScript 運行環境。vue
Node.js 事件驅動機制 + 異步 IO + 高性能 V8 引擎 ,也讓它成爲編寫高性能 Web 服務一個很是好的選擇。node
立刻 2020 年了,距離 2009 年 Node.js 開源以來,已經 10 個年頭了。git
這麼長時間的迭代,Node.js 生態圈已經很是成熟,有了不少優秀的實踐和輪子,好比 express,koa 等 web 開發框架。github
Node.js 無疑也帶動了前端生態的發展,好比前端工程化領域。web
說 Node.js 能作什麼,不如說說我用 Node.js 作了什麼吧。算法
工做中:docker
基於 express 作了一個活動頁生成工具數據庫
基於 koa + sequelize 作了一個監控系統平臺
用 Node.js 擼了一些自動化腳本,優化重複性勞做
工做之餘:
5 個有趣的 Node.js 庫,帶你走進 彩色 Node.js 世界
nodejs + docker + github pages 定製本身的 「今日頭條」
若是你最近恰好想要了解,學習 Node.js,那但願這篇文章能幫到你~
本文經過了解 Node.js 13 個 基礎核心模塊 和 一個基於 原生 Node.js 的 TodoList 實踐 ,帶你上手 Node.js !
Node.js 內置模塊遠不止 13 個,入門階段咱們瞭解一些經常使用的基礎核心模塊,就能夠上手 實踐啦~
若是不想看通篇長文,我在github 博客 將 13 個模塊拆分紅了 13 個小節,方便閱讀,每一個模塊的 demo 代碼也能在博客中找到~
爲了對 Node.js 核心模塊進一步加深理解,這個 demo 採用原生 api 實現,脫離 express,koa 等一些 web 框架和庫 。
RESTful API 實踐
靜態資源映射及 gzip 壓縮
後端路由 Router 簡易實現
Node.js 核心模塊方法實踐
實現了一個簡單的任務管理,前端採用的是 vue + element-ui ,
TodoList
└───app // 前端代碼
│ │ ...
└───controllers // 控制器
│ │ list.js // api 邏輯實現
└───router
│ │ index.js // 註冊路由
│ │ router.js // 路由實現
└───utils // 工具類
│ index.js
| data.json // 數據存放
│ index.js // 工程入口
複製代碼
實現沒有藉助任何庫,不用安裝任何依賴
node index.js
複製代碼
就能夠啓動服務,本身想要開發或者調試的話,這裏推薦使用nodemon,它實現了熱更新,能夠自動重啓.
npm install -g nodemon
nodemon
#or
nodemon index.js
複製代碼
實現效果以下:
Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。
大多數 Node.js 核心 API 都採用慣用的事件驅動架構,其中某些類型的對象(觸發器)會週期性地觸發命名事件來調用函數對象(監聽器),那麼 Node.js 是如何實現事件驅動的呢?
events 模塊是 Node.js 實現事件驅動的核心,在 node 中大部分的模塊的實現都繼承了 Events 類。好比 fs 的 readstream,net 的 server 模塊。
events 模塊只提供了一個對象: events.EventEmitter。EventEmitter 的核心就是事件觸發與事件監聽器功能的封裝,EventEmitter 本質上是一個觀察者模式的實現。
全部能觸發事件的對象都是 EventEmitter 類的實例。 這些對象有一個 eventEmitter.on() 函數,用於將一個或多個函數綁定到命名事件上。 事件的命名一般是駝峯式的字符串,但也可使用任何有效的 JavaScript 屬性鍵。
EventEmitter 對象使用 eventEmitter.emit()觸發事件,當 EventEmitter 對象觸發一個事件時,全部綁定在該事件上的函數都會被同步地調用。 被調用的監聽器返回的任何值都將會被忽略並丟棄。
下面咱們經過幾個簡單的例子來學習 events 模塊
註冊 Application 實例,繼承 EventEmitter 類,經過繼承而來的 eventEmitter.on() 函數監聽事件,eventEmitter.emit()觸發事件
const EventEmitter = require('events') /** * Expose `Application` class. * Inherits from `EventEmitter.prototype`. */ class Application extends EventEmitter {} const app = new Application() // 監聽hello事件 app.on('hello', data => { console.log(data) // hello nodeJs }) // 觸發hello事件 app.emit('hello', 'hello nodeJs') 複製代碼
綁定多個事件監聽器時,事件監聽器按照註冊的順序執行。
當監聽器函數被調用時, this 關鍵詞會被指向監聽器所綁定的 EventEmitter 實例。也可使用 ES6 的箭頭函數做爲監聽器,但 this 關鍵詞不會指向 EventEmitter 實例。
const EventEmitter = require('events') class Person extends EventEmitter { constructor() { super() } } const mrNull = new Person() // 監聽play事件 mrNull.on('play', function(data) { console.log(this) // Person { // _events: // [Object: null prototype] { play: [[Function], [Function]] }, // _eventsCount: 1, // _maxListeners: undefined // } console.log(`play`) }) // 監聽play事件 mrNull.on('play', data => { console.log(this) // {} console.log(`play again`) }) // 觸發play事件 mrNull.emit('play', 'hello nodeJs') 複製代碼
EventEmitter 以註冊的順序同步地調用全部監聽器。
const EventEmitter = require('events') class Person extends EventEmitter { constructor() { super() } } const mrNull = new Person() mrNull.on('play', function(data) { console.log(data) }) mrNull.emit('play', 'hello nodeJs') console.log(`hello MrNull`) // hello nodeJs // hello MrNull 複製代碼
監聽器函數可使用 setImmediate() 和 process.nextTick() 方法切換到異步的操做模式
const developer = new Person() developer.on('dev', function(data) { setImmediate(() => { console.log(data) }) }) developer.on('dev', function(data) { process.nextTick(() => { console.log(data) }) }) developer.emit('dev', 'hello nodeJs') console.log(`hello developer`) // hello developer // hello nodeJs // hello nodeJs 複製代碼
使用 eventEmitter.once() 能夠註冊最多可調用一次的監聽器。 當事件被觸發時,監聽器會被註銷,而後再調用。
const EventEmitter = require('events') class Person extends EventEmitter { constructor() { super() } } const mrNull = new Person() mrNull.once('play', () => { console.log('play !') }) mrNull.emit('play') mrNull.emit('play') // play ! 只輸出一次 複製代碼
在註冊事件前,觸發該事件,不會被觸發 !!
const EventEmitter = require('events') class Person extends EventEmitter { constructor() { super() } } const mrNull = new Person() mrNull.emit('play') mrNull.on('play', () => { console.log('play !') }) // 無任何輸出 複製代碼
const EventEmitter = require('events') class Person extends EventEmitter { constructor() { super() } } const mrNull = new Person() function play() { console.log('play !') } mrNull.on('play', play) mrNull.emit('play') // mrNull.off("play", play); v10.0.0版本新增,emitter.removeListener() 的別名。 // or mrNull.removeListener('play', play) mrNull.emit('play') // play ! 移除後再也不觸發 複製代碼
Node.js 提供了 path 模塊,用於處理文件路徑和目錄路徑 . 不一樣操做系統 表現有所差別 !
const path = require('path') path.dirname('/path/example/index.js') // /path/example 複製代碼
const path = require('path') path.extname('/path/example/index.js') // .js 複製代碼
const path = require('path') path.isAbsolute('/path/example/index.js') // true path.isAbsolute('.') // false 複製代碼
path.join('/path', 'example', './index.js') // /path/example/index.js 複製代碼
path.resolve('/foo/bar', './baz') // 返回: '/foo/bar/baz' path.resolve('/foo/bar', '/tmp/file/') // 返回: '/tmp/file' path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif') // 若是當前工做目錄是 /home/myself/node, // 則返回 '/home/myself/node/wwwroot/static_files/gif/image.gif' 複製代碼
path.normalize('/path///example/index.js') // /path/example/index.js 複製代碼
path.parse('/path/example/index.js') /* { root: '/', dir: '/path/example', base: 'index.js', ext: '.js', name: 'index' } */ 複製代碼
path.format({ root: '/', dir: '/path/example', base: 'index.js', ext: '.js', name: 'index' }) // /path/example/index.js 複製代碼
path.relative('/path/example/index.js', '/path') // ../.. 複製代碼
在一些場景下,咱們須要對文件進行 增刪改查等操做, Nodejs 提供了 fs 模塊,讓咱們對文件進行操做.
下面咱們來介紹幾個常常用的 API
const fs = require('fs') const fs = require('fs') // 異步讀取 fs.readFile('./index.txt', 'utf8', (err, data) => { console.log(data) // Hello Nodejs }) // 同步讀取 const data = fs.readFileSync('./index.txt', 'utf8') console.log(data) // Hello Nodejs // 建立讀取流 const stream = fs.createReadStream('./index.txt', 'utf8') // 這裏能夠看到fs.createReadStream用到了咱們前面介紹的events eventEmitter.on() 方法來監聽事件 stream.on('data', data => { console.log(data) // Hello Nodejs }) 複製代碼
寫入文件時,若是文件不存在,則會建立並寫入,若是文件存在,會覆蓋文件內容.
const fs = require('fs') // 異步寫入 fs.writeFile('./write.txt', 'Hello Nodejs', 'utf8', err => { if (err) throw err }) // 同步寫入 fs.writeFileSync('./writeSync.txt', 'Hello Nodejs') // 文件流寫入 const ws = fs.createWriteStream('./writeStream.txt', 'utf8') ws.write('Hello Nodejs') ws.end() 複製代碼
// 異步刪除文件 fs.unlink('./delete.txt', err => { if (err) throw err }) // 同步刪除文件 fs.unlinkSync('./deleteSync.txt') 複製代碼
// 異步刪除文件夾 fs.rmdir('./rmdir', err => { if (err) throw err }) // 同步刪除文件夾 fs.rmdirSync('./rmdirSync') 複製代碼
// 異步建立文件夾 fs.mkdir('./mkdir', err => { if (err) throw err }) // 同步建立文件夾 fs.mkdirSync('./mkdirSync') 複製代碼
const fs = require('fs') // 異步重命名文件 fs.rename('./rename.txt', './rename-r.txt', err => { if (err) throw err }) // 同步重命名文件夾 fs.renameSync('./renameSync', './renameSync-r') 複製代碼
const fs = require('fs') // 異步複製文件 fs.copyFile('./copy.txt', './copy-c.txt', (err, copyFiles) => { if (err) throw err }) // 同步複製文件夾 fs.copyFileSync('./null', 'null-c') 複製代碼
const fs = require('fs') // 異步獲取文件狀態 fs.stat('./dir', (err, stats) => { if (err) throw err // 是不是文件類型 console.log(stats.isFile()) // false // 是不是文件夾類型 console.log(stats.isDirectory()) // true }) // 同步獲取文件狀態 const stats = fs.statSync('./stats.txt') // 是不是文件類型 console.log(stats.isFile()) // true // 是不是文件夾類型 console.log(stats.isDirectory()) // false 複製代碼
在一些複雜的操做場景下,fs 模塊要作不少判斷與處理 ,這裏我推薦你們使用 fs-extra,它在 fs 的基礎上擴展了一些方法,讓一些複雜操做更簡便!
process 對象是一個 Global 全局對象,你能夠在任何地方使用它,而無需 require。process 是 EventEmitter 的一個實例,因此 process 中也有相關事件的監聽。使用 process 對象,能夠方便處理進程相關操做。
process.argv 是一個當前執行進程折參數組,第一個參數是 node,第二個參數是當前執行的.js 文件名,以後是執行時設置的參數列表。
node index.js --tips="hello nodejs" /* [ '/usr/local/bin/node', 'xxx/process/index.js', '--tips=hello nodejs' ] */ 複製代碼
process.execArgv 屬性會返回 Node 的命令行參數數組。
node --harmony index.js --version console.log(process.execArgv); // [ '--harmony' ] console.log(process.argv); /* [ '/usr/local/bin/node', 'xxx/process/index.js', '--version' ] */ 複製代碼
process.version 屬性會返回 Node 編譯時的版本號,版本號保存於 Node 的內置變量 NODE_VERSION 中。
console.log(process.version) // v10.15.3 複製代碼
process.pid 屬性會返回當前進程的 PID。
console.log('process PID: %d', process.pid) //process PID: 10086 複製代碼
process.cwd()方法返回進程當前的工做目錄
console.log(process.cwd()) // /Users/null/nodejs/process 複製代碼
process.exit()方法終止當前進程,此方法可接收一個退出狀態的可選參數 code,不傳入時,會返回表示成功的狀態碼 0。
process.on('exit', function(code) { console.log('進程退出碼是:%d', code) // 進程退出碼是:886 }) process.exit(886) 複製代碼
process.nextTick()方法用於延遲迴調函數的執行, nextTick 方法會將 callback 中的回調函數延遲到事件循環的下一次循環中,與 setTimeout(fn, 0)相比 nextTick 方法效率高不少,該方法能在任何 I/O 以前調用咱們的回調函數。
console.log('start') process.nextTick(() => { console.log('nextTick cb') }) console.log('end') // start // end // nextTick cb 複製代碼
process 中有三個標準備流的操做,與 其餘 streams 流操做不一樣的是,process 中流操做是同步寫,阻塞的。
process.stderr 是一個指向標準錯誤流的可寫流 Writable Stream。console.error 就是經過 process.stderr 實現的。
process.stdin 是一個指向標準輸入流的可讀流 Readable Stream。
process.stdin.setEncoding('utf8') process.stdin.on('readable', () => { let chunk // 使用循環確保咱們讀取全部的可用數據。 while ((chunk = process.stdin.read()) !== null) { if (chunk === '\n') { process.stdin.emit('end') return } process.stdout.write(`收到數據: ${chunk}`) } }) process.stdin.on('end', () => { process.stdout.write('結束監聽') }) 複製代碼
process.stdout 是一個指向標準輸出流的可寫流 Writable Stream。console.log 就是經過 process.stdout 實現的
console.log = function(d) { process.stdout.write(d + '\n') } console.log('Hello Nodejs') // Hello Nodejs 複製代碼
http 模塊是 Node.js 中很是重要的一個核心模塊。經過 http 模塊,你可使用其 http.createServer 方法建立一個 http 服務器,也可使用其 http.request 方法建立一個 http 客戶端。(本文先不說),Node 對 HTTP 協議及相關 API 的封裝比較底層,其僅能處理流和消息,對於消息的處理,也僅解析成報文頭和報文體,可是不解析實際的報文頭和報文體內容。這樣不只解決了 HTTP 本來比較難用的特性,也能夠支持更多的 HTTP 應用.
IncomingMessage 對象是由 http.Server 或 http.ClientRequest 建立的,並做爲第一參數分別傳遞給 http.Server 的'request'事件和 http.ClientRequest 的'response'事件。
它也能夠用來訪問應答的狀態、頭文件和數據等。 IncomingMessage 對象實現了 Readable Stream 接口,對象中還有一些事件,方法和屬性。
在 http.Server 或 http.ClientRequest 中略有不一樣。
實現 HTTP 服務端功能,要經過 http.createServer 方法建立一個服務端對象 http.Server。
這個方法接收一個可選傳入參數 requestListener,該參數是一個函數,傳入後將作爲 http.Server 的 request 事件監聽。不傳入時,則須要經過在 http.Server 對象的 request 事件中單獨添加。
var http = require('http') // 建立server對象,並添加request事件監聽器 var server = http.createServer(function(req, res) { res.writeHeader(200, { 'Content-Type': 'text/plain' }) res.end('Hello Nodejs') }) // 建立server對象,經過server對象的request事件添加事件事件監聽器 var server = new http.Server() server.on('request', function(req, res) { res.writeHeader(200, { 'Content-Type': 'text/plain' }) res.end('Hello Nodejs') }) 複製代碼
http.Server 對象是一個事件發射器 EventEmitter,會發射:request、connection、close、checkContinue、connect、upgrade、clientError 事件。
其中 request 事件監聽函數爲 function (request, response) { },該方法有兩個參數:request 是一個 http.IncomingMessage 實例,response 是一個 http.ServerResponse 實例。
http.Server 對象中還有一些方法,調用 server.listen 後 http.Server 就能夠接收客戶端傳入鏈接。
http.ServerResponse 對象用於響應處理客戶端請求。
http.ServerResponse 是 HTTP 服務器(http.Server)內部建立的對象,做爲第二個參數傳遞給 'request'事件的監聽函數。
http.ServerResponse 實現了 Writable Stream 接口,其對於客戶端的響應,本質上是對這個可寫流的操做。它仍是一個 EventEmitter,包含:close、finish 事件。
建立 http.Server 使用 http.createServer()方法,爲了處理客戶端請求,須要在服務端監聽來自客戶的'request'事件。
'request'事件的回調函數中,會返回一個 http.IncomingMessage 實例和一個 http.ServerResponse。
const http = require('http') /** * @param {Object} req 是一個http.IncomingMessag實例 * @param {Object} res 是一個http.ServerResponse實例 */ const server = http.createServer((req, res) => { console.log(req.headers) res.end(`Hello Nodejs`) }) server.listen(3000) 複製代碼
http.ServerResponse 實例是一個可寫流,因此能夠將一個文件流轉接到 res 響應流中。下面示例就是將一張圖片流傳送到 HTTP 響應中:
const http = require('http') /** * @param {Object} req 是一個http.IncomingMessag實例 * @param {Object} res 是一個http.ServerResponse實例 */ const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'image/jpg' }) const r = require('fs').createReadStream('./kobe.jpg') r.pipe(res) }) server.listen(3000) 複製代碼
Node.js 提供了 url 模塊,用於處理與解析 URL。
const { URL } = require("url"); const myURL = new URL("https://github.com/webfansplz#hello"); console.log(myURL); { href: 'https://github.com/webfansplz#hello', // 序列化的 URL origin: 'https://github.com', // 序列化的 URL 的 origin protocol: 'https:', // URL 的協議 username: '', // URL 的用戶名 password: '', // URL 的密碼 host: 'github.com', // URL 的主機 hostname: 'github.com', // URL 的主機名 port: '', // URL 的端口 pathname: '/webfansplz', // URL 的路徑 search: '', // URL 的序列化查詢參數 searchParams: URLSearchParams {}, // URL 查詢參數的 URLSearchParams 對象 hash: '#hello' // URL 的片斷 } 複製代碼
URL 對象屬性 除了 origin 和 searchParams 是隻讀的,其餘都是可寫的.
const { URL } = require('url') const myURL = new URL('https://github.com/webfansplz#hello') console.log(myURL.href) // https://github.com/webfansplz#hello console.log(myURL.toString()) // https://github.com/webfansplz#hello console.log(myURL.toJSON()) // https://github.com/webfansplz#hello 複製代碼
在流傳輸過程當中,爲減小傳輸數據加快傳輸速度,每每會對流進行壓縮。
HTTP 流就是如此,爲提升網站響應速度,會在服務端進行壓縮,客戶端收到數據後再進行相應的解壓。
Node.js 中的 Zlib 模塊提供了流壓縮與解壓縮功能,Zlib 模塊提供了對 Gzip/Gunzip、Deflate/Inflate、DeflateRaw/InflateRaw 類的綁定,這些類能夠實現對可讀流/可寫流的壓縮與解壓。
deflate(RFC1951)是一種壓縮算法,使用 LZ77 和哈弗曼進行編碼。gzip(RFC1952)一種壓縮格式,是對 deflate 的簡單封裝,gzip = gzip 頭(10 字節) + deflate 編碼的實際內容 + gzip 尾(8 字節)。在 HTTP 傳輸中,gzip 是一種經常使用的壓縮算法,使用 gzip 壓縮的 HTTP 數據流,會在 HTTP 頭中使用 Content-Encoding:gzip 進行標識。
HTTP Request Header 中 Accept-Encoding 是瀏覽器發給服務器,聲明瀏覽器支持的解壓類型
Accept-Encoding: gzip, deflate, br
複製代碼
HTTP Response Header 中 Content-Encoding 是服務器告訴瀏覽器 使用了哪一種壓縮類型
Content-Encoding: gzip
複製代碼
對 web 性能優化有所瞭解的同窗,相信對 gzip 都不陌生,咱們就經過 gzip 來了解 zlib 模塊.
const zlib = require('zlib') const fs = require('fs') const gzip = zlib.createGzip() const inp = fs.createReadStream('zlib.txt') const out = fs.createWriteStream('zlib.txt.gz') inp.pipe(gzip).pipe(out) 複製代碼
const zlib = require('zlib') const fs = require('fs') const gunzip = zlib.createGunzip() const inp = fs.createReadStream('./un-zlib.txt.gz') const out = fs.createWriteStream('un-zlib.txt') inp.pipe(gunzip).pipe(out) 複製代碼
const fs = require('fs') const http = require('http') const zlib = require('zlib') const filepath = './index.html' const server = http.createServer((req, res) => { const acceptEncoding = req.headers['accept-encoding'] if (acceptEncoding.includes('gzip')) { const gzip = zlib.createGzip() res.writeHead(200, { 'Content-Encoding': 'gzip' }) fs.createReadStream(filepath) .pipe(gzip) .pipe(res) } else { fs.createReadStream(filepath).pipe(res) } }) server.listen(4396) 複製代碼
流(stream)是 Node.js 中處理流式數據的抽象接口。 stream 模塊用於構建實現了流接口的對象。
Node.js 提供了多種流對象。 例如,HTTP 服務器的請求和 process.stdout 都是流的實例。
流能夠是可讀的、可寫的、或者可讀可寫的。 全部的流都是 EventEmitter 的實例。
儘管理解流的工做方式很重要,可是 stream 模塊主要用於開發者建立新類型的流實例。 對於以消費流對象爲主的開發者,極少須要直接使用 stream 模塊。
Node.js 中有四種基本的流類型:
Writable - 可寫入數據的流(例如 fs.createWriteStream())。
Readable - 可讀取數據的流(例如 fs.createReadStream())。
Duplex - 可讀又可寫的流(例如 net.Socket)。
Transform - 在讀寫過程當中能夠修改或轉換數據的 Duplex 流(例如 zlib.createDeflate())。
const http = require('http') const server = http.createServer((req, res) => { // req 是一個 http.IncomingMessage 實例,它是可讀流。 // res 是一個 http.ServerResponse 實例,它是可寫流。 let body = '' // 接收數據爲 utf8 字符串, // 若是沒有設置字符編碼,則會接收到 Buffer 對象。 req.setEncoding('utf8') // 若是添加了監聽器,則可讀流會觸發 'data' 事件。 req.on('data', chunk => { body += chunk }) // 'end' 事件代表整個請求體已被接收。 req.on('end', () => { try { const data = JSON.parse(body) // 響應信息給用戶。 res.write(typeof data) res.end() } catch (er) { // json 解析失敗。 res.statusCode = 400 return res.end(`錯誤: ${er.message}`) } }) }) server.listen(1337) // curl localhost:1337 -d "{}" // object // curl localhost:1337 -d "\"foo\"" // string // curl localhost:1337 -d "not json" // 錯誤: Unexpected token o in JSON at position 1 複製代碼
當數據能夠從流讀取時,可讀流會使用 EventEmitter API 來通知應用程序 (好比例子中的 req data 事件)。 從流讀取數據的方式有不少種。
可寫流(好比例子中的 res)會暴露了一些方法,好比 write() 和 end() 用於寫入數據到流。
可寫流和可讀流都經過多種方式使用 EventEmitter API 來通信流的當前狀態。Duplex 流和 Transform 流都是可寫又可讀的。
對於只需寫入數據到流或從流消費數據的應用程序,並不須要直接實現流的接口,一般也不須要調用 require('stream')。
對於大部分的 nodejs 開發者來講,日常並不會直接用到 stream 模塊,可是理解 stream 流的運行機制倒是尤爲重要的.
readline 模塊是一個流內容的逐行讀取模塊,經過 require('readline')引用模塊。你能夠用 readline 模塊來讀取 stdin,能夠用來逐行讀取文件流,也可用它來在控制檯和用戶進行一些交互。
const readline = require('readline') const rl = readline.createInterface({ // 監聽的可讀流 input: process.stdin, // 逐行讀取(Readline)數據要寫入的可寫流 output: process.stdout }) rl.question('你如何看待 null-cli ?', answer => { console.log(`感謝您的寶貴意見:${answer}`) rl.close() }) 複製代碼
不少有趣的 CLI 工具是基於 readline 造的哦,有興趣的同窗也能夠嘗試~
querystring 模塊是 Node.js 中的工具模塊之一,用於處理 URL 中的查詢字符串,即:querystring 部分。查詢字符串指:URL 字符串中,從問號"?"(不包括?)開始到錨點"#"或者到 URL 字符串的結束(存在#,則到#結束,不存在則到 URL 字符串結束)的部分叫作查詢字符串。querystring 模塊可將 URL 查詢字符串解析爲對象,或將對象序列化爲查詢字符串。
querystring.stringify(obj[, sep][, eq][, options])
const querystring = require('querystring') const obj = { url: 'github.com/webfansplz', name: 'null' } console.log(querystring.stringify(obj)) // url=github.com%2Fwebfansplz&name=null 複製代碼
const querystring = require('querystring') const o = querystring.parse(`url=github.com%2Fwebfansplz&name=null`) console.log(o.url) // github.com/webfansplz 複製代碼
querystring.escape 方法會對查詢字符串進行編碼,在使用 querystring.stringify 方法時可能會用到.
const str = querystring.escape(`url=github.com%2Fwebfansplz&name=null`) console.log(str) // url%3Dgithub.com%252Fwebfansplz%26name%3Dnull 複製代碼
querystring.unescape 方法是和 querystring.escape 相逆的方法,在使用 querystring.parse 方法時可能會用到。
const str = querystring.escape(`url=github.com%2Fwebfansplz&name=null`) console.log(querystring.parse(str)) // { 'url=github.com%2Fwebfansplz&name=null': '' } ✖️ console.log(querystring.parse(querystring.unescape(str))) // { url: 'github.com/webfansplz', name: 'null' } 複製代碼
Node.js 實現了一個簡單的模塊加載系統。在 Node.js 中,文件和模塊是一一對應的關係,能夠理解爲一個文件就是一個模塊。其模塊系統的實現主要依賴於全局對象 module,其中實現了 exports(導出)、require()(加載)等機制。
Node.js 中一個文件就是一個模塊。如,在 index.js 中加載同目錄下的 circle.js:
// circle.js const PI = Math.PI exports.area = r => PI * r * r exports.circumference = r => 2 * PI * r 複製代碼
// index.js const circle = require('./circle.js') console.log(`半徑爲 4 的圓面積爲 ${circle.area(4)}`) // 半徑爲 4 的圓面積爲 50.26548245743669 複製代碼
circle.js 中經過 exports 導出了 area()和 circumference 兩個方法,這兩個方法能夠其它模塊中調用。
exports 是對 module.exports 的一個簡單引用。若是你須要將模塊導出爲一個函數(如:構造函數),或者想導出一個完整的出口對象而不是作爲屬性導出,這時應該使用 module.exports。
// square.js module.exports = width => { return { area: () => width * width } } 複製代碼
// index.js const square = require('./square.js') const mySquare = square(2) console.log(`The area of my square is ${mySquare.area()}`) // The area of my square is 4 複製代碼
當 Node.js 直接運行一個文件時,require.main 屬性會被設置爲 module 自己。這樣,就可經過這個屬性判斷模塊是否被直接運行:
require.main === module 複製代碼
好比,對於上面例子的 index.js 來講, node index.js 上面值就是 true, 而經過 require('./index')時, 值倒是 false.
module 提供了一個 filename 屬性,其值一般等於__filename。 因此,當前程序的入口點能夠經過 require.main.filename 來獲取。
console.log(require.main.filename === __filename) // true 複製代碼
使用 require.resolve()函數,能夠獲取 require 加載的模塊的確切文件名,此操做只返回解析後的文件名,不會加載該模塊。
console.log(require.resolve('./square.js')) // /Users/null/meet-nodejs/module/square.js 複製代碼
require.resolve 的工做過程:
require(X) from module at path Y 1. If X is a core module, a. return the core module b. STOP 2. If X begins with './' or '/' or '../' a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 3. LOAD_NODE_MODULES(X, dirname(Y)) 4. THROW "not found" LOAD_AS_FILE(X) 1. If X is a file, load X as JavaScript text. STOP 2. If X.js is a file, load X.js as JavaScript text. STOP 3. If X.json is a file, parse X.json to a JavaScript Object. STOP 4. If X.node is a file, load X.node as binary addon. STOP LOAD_AS_DIRECTORY(X) 1. If X/package.json is a file, a. Parse X/package.json, and look for "main" field. b. let M = X + (json main field) c. LOAD_AS_FILE(M) 2. If X/index.js is a file, load X/index.js as JavaScript text. STOP 3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP 4. If X/index.node is a file, load X/index.node as binary addon. STOP LOAD_NODE_MODULES(X, START) 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE c. DIR = path join(PARTS[0 .. I] + "node_modules") b. DIRS = DIRS + DIR c. let I = I - 1 5. return DIRS 複製代碼
模塊在第一次加載後會被緩存到 require.cache 對象中, 今後對象中刪除鍵值對將會致使下一次 require 從新加載被刪除的模塊。
屢次調用 require('index'),未必會致使模塊中代碼的屢次執行。這是一個重要的功能,藉助這一功能,能夠返回部分完成的對象;這樣,傳遞依賴也能被加載,即便它們可能致使循環依賴。
若是你但願一個模塊屢次執行,那麼就應該輸出一個函數,而後調用這個函數。
模塊的基於其解析後的文件名進行緩存。因爲調用的位置不一樣,可能會解析到不一樣的文件(如,須要從 node_modules 文件夾加載的狀況)。因此,當解析到其它文件時,就不能保證 require('index')老是會返回確切的同一對象。
另外,在不區分大小寫的文件系統或系統中,不一樣的文件名可能解析到相同的文件,但緩存仍會將它們視爲不一樣的模塊,會屢次加載文件。如:require('./index')和 require('./INDEX')會返回兩個不一樣的對象,不管'./index'和'./INDEX'是不是同一個文件。
當 require()存在循環調用時,模塊在返回時可能並不會被執行。
// a.js console.log('a starting') exports.done = false const b = require('./b.js') console.log('in a, b.done = %j', b.done) exports.done = true console.log('a done') 複製代碼
// b.js console.log('b starting') exports.done = false const a = require('./a.js') console.log('in b, a.done = %j', a.done) exports.done = true console.log('b done') 複製代碼
// main.js console.log('main starting') const a = require('./a.js') const b = require('./b.js') console.log('in main, a.done=%j, b.done=%j', a.done, b.done) 複製代碼
首先 main.js 會加載 a.js,接着 a.js 又會加載 b.js。這時,b.js 又會嘗試去加載 a.js。
爲了防止無限的循環,a.js 會返回一個 unfinished copy 給 b.js。而後 b.js 就會中止加載,並將其 exports 對象返回給 a.js 模塊。
這樣 main.js 就完成了 a.js、b.js 兩個文件的加載。輸出以下:
$ node main.js main starting a starting b starting in b, a.done = false b done in a, b.done = true a done in main, a.done=true, b.done=true 複製代碼
當加載文件模塊時,若是按文件名查找未找到。那麼 Node.js 會嘗試添加.js 和.json 的擴展名,並再次嘗試查找。若是仍未找到,那麼會添加.node 擴展名再次嘗試查找。
對於.js 文件,會將其解析爲 JavaScript 文本文件;而.json 會解析爲 JOSN 文件文件;.node 會嘗試解析爲編譯後的插件文件,並由 dlopen 進行加載。
當加載的文件模塊使用'/'前綴時,則表示絕對路徑。如,require('/home/null/index.js')會加載/home/null/index.js 文件。
而使用'./'前綴時,表示相對路徑。如,在 index.js 中 require('./circle')引用時,circle.js 必須在相同的目錄下才能加載成功。
當沒有'/'或'./'前綴時,所引用的模塊必須是「核心模塊」或是 node_modules 中的模塊。
若是所加載的模塊不存在,require()會拋出一個 code 屬性爲'MODULE_NOT_FOUND'的錯誤。
當前模塊的目錄名。 與 __filename 的 path.dirname() 相同。
console.log(__dirname) // /Users/null/meet-nodejs/module console.log(require('path').dirname(__filename)) // /Users/null/meet-nodejs/module console.log(__dirname === require('path').dirname(__filename)) // true 複製代碼
module 在每一個模塊中表示對當前模塊的引用。 而 module.exports 又能夠經過全局對象 exports 來引用。module 並非一個全局對象,而更像一個模塊內部對象。
這個模塊引入的全部模塊對象
module.exports 經過模塊系統建立。有時它的工做方式與咱們所想的並不一致,有時咱們但願模塊是一些類的實例。所以,要將導出對象賦值給 module.exports,可是導出所需的對象將分配綁定本地導出變量,這可能不是咱們想要的結果。
// a.js const EventEmitter = require('events') module.exports = new EventEmitter() // Do some work, and after some time emit // the 'ready' event from the module itself. setTimeout(() => { module.exports.emit('ready') }, 1000) 複製代碼
const a = require('./a') a.on('ready', () => { console.log('module a is ready') }) 複製代碼
須要注意,分配給 module.exports 的導出值必須能馬上獲取到,當使用回調時其不能正常執行。
exports 能夠作爲 module.exports 的一個引用。和任何變量同樣,若是爲它分配新值,其舊值將會失效:
function require(...) { // ... ((module, exports) => { // Your module code here exports = some_func; // re-assigns exports, exports is no longer // a shortcut, and nothing is exported. module.exports = some_func; // makes your module export 0 })(module, module.exports); return module; } 複製代碼
module.filename - 模塊解析後的完整文件名
module.id - 用於區別模塊的標識符,一般是徹底解析後的文件名。
module.loaded - 模塊是否加載完畢
module.parent - 父模塊,即:引入這個模塊的模塊
module.require(id)
module.require 提供了相似 require()的功能,能夠從最初的模塊加載一個模塊
在引入 TypedArray 以前,JavaScript 語言沒有用於讀取或操做二進制數據流的機制。 Buffer 類是做爲 Node.js API 的一部分引入的,用於在 TCP 流、文件系統操做、以及其餘上下文中與八位字節流進行交互。
console.log(Buffer.from([1, 2, 3, 4, 5])) // <Buffer 01 02 03 04 05> console.log(Buffer.from(new ArrayBuffer(8))) // <Buffer 00 00 00 00 00 00 00 00> console.log(Buffer.from('Hello world')) // <Buffer 48 65 6c 6c 6f 20 77 6f 72 6c 64> 複製代碼
當字符串數據被存儲入 Buffer 實例或從 Buffer 實例中被提取時,能夠指定一個字符編碼。
// 緩衝區轉換爲 UTF-8 格式的字符串 const buffer = Buffer.from('Hello world') console.log(buffer.toString()) // Hello world 複製代碼
// 緩衝區數據轉換爲base64格式字符串 const buffer = Buffer.from('Hello world') console.log(buffer.toString('base64')) // SGVsbG8gd29ybGQ= 複製代碼
// 將base64編碼的字符串,轉換爲UTF-8編碼 const buffer = Buffer.from('Hello world') const base64Str = buffer.toString('base64') const buf = Buffer.from(base64Str, 'base64') console.log(buf.toString('utf8')) // Hello world 複製代碼
DNS(Domain Name System,域名系統),DNS 協議運行在 UDP 協議之上,使用端口號 53。DNS 是因特網上做爲域名和 IP 地址相互映射的一個分佈式數據庫,可以使用戶更方便的訪問互聯網,而不用去記住可以被機器直接讀取的 IP 數串。簡單的說,就是把域名(網址)解析成對應的 IP 地址。Node.js 的 dns 模塊,提供了 DNS 解析功能。當使用 dns 模塊中的 net.connect(80, 'github.com/webfansplz')方法 或 http 模塊的 http.get({ host: 'github.com/webfansplz' })方法時,在其底層會使用 dns 模塊中的 dns.lookup 方法進行域名解析。
使用操做系統底層的 DNS 服務進行域名解析時,不須要鏈接到網絡僅使用系統自帶 DNS 解析功能。這個功能由 dns.lookup()方法實現。
dns.lookup(hostname[, options], callback):將一個域名(如:'www.baidu.com')解析爲第一個找到的 A 記錄(IPv4)或 AAAA 記錄(IPv6)
hostname 表示要解析的域名。
options 能夠是一個對象或整數。若是沒有提供 options 參數,則 IP v4 和 v6 地址均可以。若是 options 是整數,則必須是 4 或 6。若是 options 是對象時,會包含如下兩個可選參數:
family:可選,IP 版本。若是提供,必須是 4 或 6。不提供則,IP v4 和 v6 地址均可以
hints:可選。若是提供,能夠是一個或者多個 getaddrinfo 標誌。若不提供,則沒有標誌會傳給 getaddrinfo。
callback 回調函數,參數包含(err, address, family)。出錯時,參數 err 是 Error 對象。address 參數表示 IP v4 或 v6 地址。family 參數是 4 或 6,表示 address 協議版本。
const dns = require('dns') dns.lookup(`www.github.com`, (err, address, family) => { if (err) throw err console.log('地址: %j 地址族: IPv%s', address, family) // 地址: "13.229.188.59" 地址族: IPv4 }) 複製代碼
在 dns 模塊中,除 dns.lookup()方法外都是使用 DNS 服務器進行域名解析,解析時須要鏈接到網絡。
dns.resolve(hostname[, rrtype], callback):將一個域名(如 'www.baidu.com')解析爲一個 rrtype 指定類型的數組
hostname 表示要解析的域名。
rrtype 有如下可用值:
rrtype | records 包含 | 結果的類型 | 快捷方法 |
---|---|---|---|
'A' | IPv4 地址 (默認) | string | dns.resolve4() |
'AAAA' | IPv6 地址 | string | dns.resolve6() |
'ANY' | 任何記錄 | Object | dns.resolveAny() |
'CNAME' | 規範名稱記錄 | string | dns.resolveCname() |
'MX' | 郵件交換記錄 | Object | dns.resolveMx() |
'NAPTR' | 名稱權限指針記錄 | Object | dns.resolveNaptr() |
'NS' | 名稱服務器記錄 | string | dns.resolveNs() |
'PTR' | 指針記錄 | string | dns.resolvePtr() |
'SOA' | 開始受權記錄 | Object | dns.resolveSoa() |
'SRV' | 服務記錄 | Object | dns.resolveSrv() |
'TXT' | 文本記錄 | string[] | dns.resolveTxt() |
callback 回調函數,參數包含(err, addresses)。出錯時,參數 err 是 Error 對象。addresses 根據記錄類型的不一樣返回值也不一樣。
const dns = require('dns') dns.resolve('www.baidu.com', 'A', (err, addresses) => { if (err) throw err console.log(`IP地址 : ${JSON.stringify(addresses)}`) // IP地址 : ["163.177.151.110","163.177.151.109"] }) // or dns.resolve4('www.baidu.com', (err, addresses) => { if (err) throw err console.log(`IP地址 : ${JSON.stringify(addresses)}`) // IP地址 : ["163.177.151.110","163.177.151.109"] }) 複製代碼
使用 getnameinfo 方法將傳入的地址和端口解析爲域名和服務
dns.reverse(ip, callback)
ip 表示要反向解析的 IP 地址。
callback 回調函數,參數包含(err, domains)。出錯時,參數 err 是 Error 對象。domains 解析後的域名數組。
dns.reverse('8.8.8.8', (err, domains) => { if (err) throw err console.log(domains) // [ 'dns.google' ] }) 複製代碼
dns.lookupService(address, port, callback)
address 表示要解析的 IP 地址字符串。
port 表示要解析的端口號。
callback 回調函數,參數包含(err, hostname, service)。出錯時,參數 err 是 Error 對象。
dns.lookupService('127.0.0.1', 80, function(err, hostname, service) { if (err) throw err console.log('主機名:%s,服務類型:%s', hostname, service) // 主機名:localhost,服務類型:http }) 複製代碼
若是你和我同樣喜歡前端,也愛動手摺騰,歡迎關注我一塊兒玩耍啊~ ❤️
前端時刻