一、Node.js 簡介
Node.js 是一個基于 Chrome V8 引擎 的 JavaScript 運行時環(huán)境
安裝與運行
下載
https://nodejs.org/zh-cn/download/

創(chuàng)建項目
mkdir node
cd node
npm init -y
新建 index.js 文件
const { readFile } = require('fs')
readFile('./package.json', { encoding: 'utf-8' }, (err, data) => {
if (err) {
throw err;
}
console.log(data)
})
輸入 package.json 文件
node index.js
版本管理
在同一個設(shè)備上如何快速切換Node.js 版本?
版本管理工具:
- n: 一個npm 全局的開源包,是依賴npm 來全局安裝、使用的
- fnm: 快速簡單,兼容性支持.node-version和.nvmrc文件
- nvm: 獨立的軟件包,Node Version Manager
特點

異步 I/O
當 Node.js 執(zhí)行 I/O 操作時,會在響應返回并恢復操作,而不是阻塞線程并浪費CPU 循環(huán)等待

單線程
Node.js 保持了 JavaScript 在瀏覽器中單線程的特點
優(yōu)點:
- 不用處處在意狀態(tài)同步問題,不會發(fā)生死鎖
- 沒有線程上下文切換帶來的性能開銷
缺點:
- 無法利用多核 CPU
- 錯誤會引起整個應用退出,健壯性不足
- 大量計算占用導致CPU,無法繼續(xù)執(zhí)行
瀏覽器為例,瀏覽器是多進程,JS 引擎單線程
Browser 進程:瀏覽器主進程,只有一個
插件進程:插件使用時才創(chuàng)建
GPU 進程:最多一個用于3D 繪制
渲染進程:頁面渲染、JS執(zhí)行、事件處理
- GUI 渲染線程+
- JS 引擎線程+ V8
- 事件觸發(fā)線程
- 定時器觸發(fā)線程
- 異步請求
跨平臺
兼容Windows 和*nix 平臺,主要得益于在操作系統(tǒng)與Node 上層模塊系統(tǒng)之間構(gòu)建了一層平臺架構(gòu)

應用場景
Node.js 在大部分領(lǐng)域都占有一席之地,尤其是I/O密集型的
- Web 應用:Express / Koa
- 前端構(gòu)建: WebpackGUI
- 客戶端軟件: VSCode / 網(wǎng)易云音樂
- 其它:實時通訊、爬蟲、CLI 等
二、模塊化機制
- 何為模塊化?
根據(jù)功能或業(yè)務將一個大程序拆分成互相依賴的小文件,再用簡單的方式拼裝起來 - 為什么模塊化?
無模塊化問題所有script 標簽必須保證順序正確,否則會依賴報錯
全局變量存在命名沖突,占用內(nèi)存無法被回收
IIFE/namespace 會導致代碼可讀性低等諸多問題
CommonJS規(guī)范
Node.js 支持 CommonJS 模塊規(guī)范,采用同步機制加載模塊
// greeting.js
const prefix = 'hello';
const sayHi = function () {
return prefix + 'world';
}
module.exports = {
sayHi
}
// index.js
const { sayHi } = require('./greeting');
sayHi();
exports = module.exports = { }
// greeting.js
const prefix = 'hello';
const sayHi = function () {
return `${prefix} world`;
}
exports.sayHi = sayHi;
// index.js
const { sayHi } = require('./greeting');
sayHi();
CommonJS 中 exports、require、module、__filename、__dirname 變量
function (exports, require, module, __filename, __dirname) {
const m = 1;
module.exports.m = m;
}
加載方式:
- 加載內(nèi)置模塊require('fs')
- 加載相對 | | 絕對路徑的文件模塊
require('/User/.../file.js')
require('./file.js') - 加載 npm 包 require('lodash')
npm 包查找原則:
require('lodash')
- 當前目錄node_modules
- 如果沒有,父級目錄的node_modules
- 如果沒有,沿著路徑向上遞歸,直到根目錄下node_modules
- 找到之后會加載package.json main 指向的文件,如果沒有package.json 則依次查找index.js、index.json、index.node
require.cache
require.cache 中緩存著加載過的模塊,緩存的原因:同步加載
- 文件模塊查找耗時,如果每次require 都需要重新遍歷查找,性能會比較差;
- 在實際開發(fā)中,模塊可能包含副作用代碼
// 有緩存
const mod1 = require('./foo');
const mod2 = require('./foo');
console.log(mod1 === mod2); // true
//無緩存
function requireUncached(module) {
delete require.cache[require.resolve(module)];
return require(module);
}
const mod3 = requireUncached('./foo');
console.log(mod1 === mod3); // false
其他模塊化規(guī)范
- AMD 是 RequireJS 在推廣過程中規(guī)范化產(chǎn)出,異步加載,推崇依賴前置
- CMD 是 SeaJS 在推廣過程中規(guī)范化產(chǎn)出,異步加載,推崇就近依賴
- UMD (Universal Module Definition) 規(guī)范,兼容 AMD 和 CommonJS 模式
- ES Modules (ESM),語言層面的模塊化規(guī)范,與環(huán)境無關(guān),可借助 babel 編譯
常用模塊介紹
文件
var fs = require("fs") // 引入 fs 模塊
fs.readFile(filename, [options], callback); //讀取文件
fs.writeFile(filename, data, [options], callback); // 寫文件
fs.appendFile(filename, data, [options], callback); //以追加的方式寫文件
fs.open(filename, flags, [mode], callback); //打開文件
fs.mkdir(path, [mode], callback); //創(chuàng)建目錄:
fs.readdir(path, callback); //讀取目錄
fs.exists(path, callback); //查看文件與目錄是否存在
Path 模塊
var path = require("path") // 引入 path 模塊
path.basename(path[, ext]); //返回path的最后-部分
path.dirname(path); // 返回path的目錄名
path.normalize(path);//路徑解析,得到規(guī)范路徑
path.isAbsolute(path); //判斷路徑是否是絕對路徑
path.relative(form,to); //方法根據(jù)當前工作目錄返回從from 到to的相對路徑
path.resolve([...paths]); //將路徑或路徑片段的序列解析為絕對路徑
OS 模塊
var os = require("os") //引入 os 模塊
os.cpus(); //返回每個邏輯cpu內(nèi)核信息
os.hostname(); //返回操作系統(tǒng)的主機名
os.platform(); //返回標識操作系統(tǒng)平臺的字符串
os.userInfo([options]); //返回關(guān)于當前有效用戶的信息
三、包管理機制
npm介紹
NPM 是Node.js 中的包管理器,提供了安裝、刪除等其它命令來管理包
常用命令:
- npm init
- npm config
- npm run [cmd]
- npm install [pkg]
- npm uninstall [pkg]
- npm update [pkg]
- npm info [pkg]
- npm publish
package.json 文件
- name 包名稱
- version 版本號
- main 入口文件
- scripts 執(zhí)行腳本
- dependencies 線上依賴
- devDependencies 開發(fā)依賴
- repository 代碼托管地址
更多 package.json 配置
https://docs.npmjs.com/cli/v7/configuring-npm/package-json
依賴
- dependencies 業(yè)務依賴,應用發(fā)布后正常執(zhí)行所需要的包
- devDependencies 開發(fā)依賴,只用于開發(fā)環(huán)境
- peerDependencies 同等依賴,比如一個webpack 插件依賴特定版本的webpack
- bundledDependencies 打包依賴(npm run pack),必須已經(jīng)在devDep 或者dep聲明過
- optionalDependencies 可選依賴
私有 npm
- 鏡像公司內(nèi)部私有 npm
- 鏡像設(shè)置npm config set registry=https://bnpm.byted.org
其它
- 并行安裝
- 扁平管理
- 鎖文件(lockfile)
- 緩存優(yōu)化
- ...
- npm7 | yarn => lock/扁平/緩存...
- pnpm => monorepo/硬、符號鏈接/安全性高...
四、異步編程
Callback
目的:讀取 package.json 中main 字段對應的文件內(nèi)容
問題:如何解決回調(diào)地獄?
const fs = require('fs')
fs.readFile('./package.json', { encoding: 'utf-8' }, (err, data) => {
if (err) throw err;
const { main } = JSON.parse(data);
fs.readFile(main, { encoding: 'utf-8' }, (err, data) => {
if (err) throw err;
console.log(data)
})
})
Promise
Promise 是一個具有四個狀態(tài)的有限狀態(tài)機,其中三個核心狀態(tài)為 Pending(掛起),F(xiàn)ulfilled(完成)、Rejected(拒絕),以及還有一個未開始狀態(tài)。

使用 Promise , 實現(xiàn)讀取 package.json 中 main 字段對應的文件內(nèi)容
const { readFile } = require('fs/promises')
readFile('./package.json', { encoding: 'utf-8' }).then(res => {
return JSON.parse(res);
}).then(data => {
return readFile(data.main, { encoding: 'utf-8' });
}).then(res => {
console.log(res);
})
如何將 Callback 轉(zhuǎn)為 Promise 形式?ugl.promisify
function promisify(fn, receiver) {
return (...args) => {
return new Promise((resolve, reject) => {
fn.apply(receiver, [...args, (err, res) => {
return err ? reject(err) : resolve(res);
}]);
});
};
}
const readFilePromise = promisify(fs.readFile, fs);
await
await 函數(shù)使用 try catch 捕獲異常(注意并行處理)
const { readFile } = require('fs/promises')
async () => {
const { main } = JSON.parse(await readFile('./package.json', { encoding: 'utf-8' }));
const data = await readFile(main, { encoding: 'utf-8' });
console.log(data);
}
Event
發(fā)布訂閱模式,Node.js 內(nèi)置events 模塊
比如 HTTP server on('request') 事件監(jiān)聽
//發(fā)布訂閱模式
const EventEmitter = require('events');
class MyEmitter extends EventEmitter { }
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
// server 監(jiān)聽request 事件
const http = require('http');
const server = http.createServer((req, res) => {
res.end('hello')
});
server.on('request', (req, res) => {
console.log(req.url);
});
server.listen(3000);
五、Web 應用開發(fā)
HTTP 模塊
搭建一個最簡單的HTTP 服務?Node.js 內(nèi)置HTTP 模塊
const http = require('http');
http.createServer((req, res) => {
res.end('hello World\n');
}).listen(3000, () => {
console.log('App running at http://127.0.0.1:3000/')
})
Koa 介紹
Koa —— 基于 Node.js 平臺的下一代Web 開發(fā)框架 Koa 它僅僅提供了一個輕量優(yōu)雅的函數(shù)庫,使得編寫Web 應用變得得心應手, 不在內(nèi)核方法中綁定任何中間件
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000, () => {
console.log('App running at http://127.0.0.1:3000/')
});
執(zhí)行過程
- 服務啟動
- 實例化 application
- 注冊中間件
- 創(chuàng)建服務、監(jiān)聽端口
- 接受/處理請求
- 獲取請求req、res 對象
- req -> request、res -> response 封裝
- request & response -> context
- 執(zhí)行中間件
- 輸出設(shè)置到ctx.body 上的內(nèi)容
Koa 中間件
Koa 應用程序是一個包含一組中間件函數(shù)的對象,它是按照洋蔥模型組織和執(zhí)行的

常用中間件
- koa-router:路由解析
- koa-body: request body 解析
- koa-logger:日志記錄
- koa-views: 模板渲染
- koa2-cors :跨域處理
- koa-session:session 處理
- koa-helmet:安全防護
- ...
Koa 中間件繁多,質(zhì)量參差不齊,需要合理選擇,高效組合...
基于Koa 的前端框架
開源:ThinkJS / Egg ...
內(nèi)部:Turbo、Era、Gulu ...
它們做了什么?
- Koa 對象 response / request / context / application 等擴展
- Koa 常用中間件庫
- 公司內(nèi)部服務支持
- 進程管理
- 腳手架