一、Node.js 简介
Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
安装与运行
下载
https://nodejs.org/zh-cn/download/
创建项目
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
版本管理
在同一个设备上如何快速切换Node.js 版本?
版本管理工具:
- n: 一个npm 全局的开源包,是依赖npm 来全局安装、使用的
- fnm: 快速简单,兼容性支持.node-version和.nvmrc文件
- nvm: 独立的软件包,Node Version Manager
特点
异步 I/O
当 Node.js 执行 I/O 操作时,会在响应返回并恢复操作,而不是阻塞线程并浪费CPU 循环等待
单线程
Node.js 保持了 JavaScript 在浏览器中单线程的特点
优点:
- 不用处处在意状态同步问题,不会发生死锁
- 没有线程上下文切换带来的性能开销
缺点:
- 无法利用多核 CPU
- 错误会引起整个应用退出,健壮性不足
- 大量计算占用导致CPU,无法继续执行
浏览器为例,浏览器是多进程,JS 引擎单线程
Browser 进程:浏览器主进程,只有一个
插件进程:插件使用时才创建
GPU 进程:最多一个用于3D 绘制
渲染进程:页面渲染、JS执行、事件处理
- GUI 渲染线程+
- JS 引擎线程+ V8
- 事件触发线程
- 定时器触发线程
- 异步请求
跨平台
兼容Windows 和*nix 平台,主要得益于在操作系统与Node 上层模块系统之间构建了一层平台架构
应用场景
Node.js 在大部分领域都占有一席之地,尤其是I/O密集型的
- Web 应用:Express / Koa
- 前端构建: WebpackGUI
- 客户端软件: VSCode / 网易云音乐
- 其它:实时通讯、爬虫、CLI 等
二、模块化机制
- 何为模块化?
根据功能或业务将一个大程序拆分成互相依赖的小文件,再用简单的方式拼装起来 - 为什么模块化?
无模块化问题所有script 标签必须保证顺序正确,否则会依赖报错
全局变量存在命名冲突,占用内存无法被回收
IIFE/namespace 会导致代码可读性低等诸多问题
CommonJS规范
Node.js 支持 CommonJS 模块规范,采用同步机制加载模块
// 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;
}
加载方式:
- 加载内置模块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 都需要重新遍历查找,性能会比较差;
- 在实际开发中,模块可能包含副作用代码
// 有缓存
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
其他模块化规范
- AMD 是 RequireJS 在推广过程中规范化产出,异步加载,推崇依赖前置
- CMD 是 SeaJS 在推广过程中规范化产出,异步加载,推崇就近依赖
- UMD (Universal Module Definition) 规范,兼容 AMD 和 CommonJS 模式
- ES Modules (ESM),语言层面的模块化规范,与环境无关,可借助 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); //创建目录:
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);//路径解析,得到规范路径
path.isAbsolute(path); //判断路径是否是绝对路径
path.relative(form,to); //方法根据当前工作目录返回从from 到to的相对路径
path.resolve([...paths]); //将路径或路径片段的序列解析为绝对路径
OS 模块
var os = require("os") //引入 os 模块
os.cpus(); //返回每个逻辑cpu内核信息
os.hostname(); //返回操作系统的主机名
os.platform(); //返回标识操作系统平台的字符串
os.userInfo([options]); //返回关于当前有效用户的信息
三、包管理机制
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 执行脚本
- dependencies 线上依赖
- devDependencies 开发依赖
- repository 代码托管地址
更多 package.json 配置
https://docs.npmjs.com/cli/v7/configuring-npm/package-json
依赖
- dependencies 业务依赖,应用发布后正常执行所需要的包
- devDependencies 开发依赖,只用于开发环境
- peerDependencies 同等依赖,比如一个webpack 插件依赖特定版本的webpack
- bundledDependencies 打包依赖(npm run pack),必须已经在devDep 或者dep声明过
- optionalDependencies 可选依赖
私有 npm
- 镜像公司内部私有 npm
- 镜像设置npm config set registry=https://bnpm.byted.org
其它
- 并行安装
- 扁平管理
- 锁文件(lockfile)
- 缓存优化
- ...
- npm7 | yarn => lock/扁平/缓存...
- pnpm => monorepo/硬、符号链接/安全性高...
四、异步编程
Callback
目的:读取 package.json 中main 字段对应的文件内容
问题:如何解决回调地狱?
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 是一个具有四个状态的有限状态机,其中三个核心状态为 Pending(挂起),Fulfilled(完成)、Rejected(拒绝),以及还有一个未开始状态。
使用 Promise , 实现读取 package.json 中 main 字段对应的文件内容
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 转为 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 函数使用 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
发布订阅模式,Node.js 内置events 模块
比如 HTTP server on('request') 事件监听
//发布订阅模式
const EventEmitter = require('events');
class MyEmitter extends EventEmitter { }
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
// server 监听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 应用开发
HTTP 模块
搭建一个最简单的HTTP 服务?Node.js 内置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 开发框架 Koa 它仅仅提供了一个轻量优雅的函数库,使得编写Web 应用变得得心应手, 不在内核方法中绑定任何中间件
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/')
});
执行过程
- 服务启动
- 实例化 application
- 注册中间件
- 创建服务、监听端口
- 接受/处理请求
- 获取请求req、res 对象
- req -> request、res -> response 封装
- request & response -> context
- 执行中间件
- 输出设置到ctx.body 上的内容
Koa 中间件
Koa 应用程序是一个包含一组中间件函数的对象,它是按照洋葱模型组织和执行的
常用中间件
- koa-router:路由解析
- koa-body: request body 解析
- koa-logger:日志记录
- koa-views: 模板渲染
- koa2-cors :跨域处理
- koa-session:session 处理
- koa-helmet:安全防护
- ...
Koa 中间件繁多,质量参差不齐,需要合理选择,高效组合...
基于Koa 的前端框架
开源:ThinkJS / Egg ...
内部:Turbo、Era、Gulu ...
它们做了什么?
- Koa 对象 response / request / context / application 等扩展
- Koa 常用中间件库
- 公司内部服务支持
- 进程管理
- 脚手架