macOS或Linux的nvm:
nvm list-remote
nvm install v新版本号
nvm use v新版本号
nvm alias default v新版本号
nvm list
nvm uninstall v旧版本号 # 若无法删除,则用管理员权限按要求设置文件夹权限,还可以去目录删除 /Users/「用户名」/.nvm/versions/node/v「版本号」
安装的全局软件包位置:
/Users/「用户名」/.nvm/versions/node/v「版本号」/lib/node_modules
Windows的nvm-windows:
安装nvm-windows时,需要删除原本安装在电脑上的Node.js。
nvm node_mirror https://npm.taobao.org/mirrors/node/ # 设置node源
nvm npm_mirror https://npm.taobao.org/mirrors/npm/ # 设置npm源
nvm list available
nvm install 新版本号
nvm use 新版本号
nvm list
nvm uninstall 旧版本号 # 还可以再去目录中删除 C:\Users\「用户名」\AppData\Roaming\nvm\v「版本号」
切换版本之后需重装Node.js的全局模块包。
不支持Windows。
安装
npm install -g n
# 或:brew、curl
切换版本
n ls
# 安装最新的长期支持正式发布版本
n lts
# 安装最新正式发布版本
n latest
# 安装指定版本
n 新版本号
# 展示已经安装的版本,并选择使用哪个版本
n
# 删除指定版本
n rm 旧版本号
# 删除当前版本之外的所有版本
n prune
# 删除当前版本安装的node和npm
n uninstall
npm(Node Package Manager)。
npm
CI
在任意命令后添加
-h
、--help
查看当前命令的所有参数。
制作
登录
npm login
初始化package.json
npm init
修改初始化信息
npm set init.author.name "名字"
npm set init.author.email "邮箱"
npm set init.license "MIT"
初始化信息会存放在
~/.npmrc
文件里。
调试开发
npm link
、npm unlink
仅本地
引用本地模块的仓库:npm link 本地模块的路径
取消:
引用本地模块的仓库:npm unlink 模块名
全局
本地模块:npm link
也支持bin。
取消:
本地模块:npm unlink
引用本地模块的仓库:npm link 模块名
取消:
引用本地模块的仓库:npm unlink 模块名
发布(默认:发布至latest
标签)
npm publish [--tag <tag>]
- 除了latest,其他标签都不会默认被安装。最后推送的latest版本会显示在npm官网。
- 注意:设置源为npm的网站(
https://registry.npmjs.org/
)才可以推送到npm。
「下线」
npm unpublish [<@scope>/]<pkg>[@<version>]
只能下线24小时内发布的版本。
npm deprecate <pkg>[@<version>] <message>
打印登录名
npm whoami
登出
npm logout
查看信息
查看模块官方信息
npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...]
npm view <pkg> versions
:展示所有版本查看已安装的模块和依赖
npm list [[<@scope>/]<pkg> ...]
仅查看顶层依赖:
npm list --depth=0
查看已安装模块是否需要升级
npm outdated [[<@scope>/]<pkg> ...]
查看、添加、删除仓库标签的最后版本
每个标签仅保留最后一个版本;latest标签无法删除。
npm dist-tag ls [<pkg>]
npm dist-tag add <pkg>@<version> [<tag>]
npm dist-tag rm <pkg> <tag>
安装
改变安装包的顺序会影响安装包的内容和依赖。
手动安装npm自己
node_modules\npm
;node_modules\npm
;node_modules\npm\bin
下面的npm
、npm.cmd
(、npx
、npx.cmd
)到本地Node.js同目录下。安装包
自动选择范围内最后发布的版本,安装到本地或全局的node_modules
。全局安装会额外创建系统命令。
安装方式
npm install
安装所在目录的package.json
文件描述内容
最新(主版本号最新,第一个数):
*
、x
次版本号最新(第二个数):
1
、1.x
、^1.2.3
补丁号最新(第三个数):
1.2
、1.2.x
、~1.2.3
确定的版本:
1.2.3
主版本号.次版本号.补丁号
,详细定义查看Semantic。
npm install [<@scope>/]<name>[@<tag>]
@内容
版本范围:>
、>=
、<
、<=
+ 版本号。范围中最后发布的版本。
e.g.
npm install npm-devil@">=0.0.1 <0.0.5"
参数
--force
、-f
:强制重新安装。
当目录中已经存在指定模块,默认:不会重新安装已经安装的模块。或删除
node_modules
目录再重新安装。
--save
、-S
:安装信息保存到package.json
的dependencies
(执行时依赖插件)。--save-dev
、-D
:安装信息保存到package.json
的devDependencies
(开发时依赖插件)。--save-optional
、-O
:安装信息保存到package.json
的optionalDependencies
。作用域
require
引入后使用。升级
npm update [-g] [<pkg>...]
升级成功会把升级版本号自动写入package.json
。
只更新顶层模块,而不更新依赖的依赖。可以使用
npm --depth 9999 update
更新依赖的依赖。
升级npm自己
npm install -g npm
Windows:
在PowerShell内执行
Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force
npm install -g npm-windows-upgrade
npm-windows-upgrade
卸载
npm uninstall [<@scope>/]<pkg>[@<version>]... [--save-prod|--save-dev|--save-optional] [--no-save]
重装npm
curl -L https://www.npmjs.org/install.sh | sh
若还是无法使用npm,建议重装Node.js。
验证缓存(垃圾收集不需要的数据、验证缓存的完整性)
npm cache verify
除非回收磁盘空间,否则不要使用以下清空npm缓存
npm cache clean -f
package-lock.json
更新成功(npm update
)或第一次安装(npm install
)时生成,用以记录当前状态下实际安装的各个npm包的具体来源和版本号。在存在此文件的根目录进行npm install
,会按照完全相同的依赖关系进行安装。
- 若使用lock机制,则应该将
package-lock.json
提交到版本控制。- 若不使用lock机制,则应该把
package-lock=false
加入.npmrc
,并把.npmrc
提交到版本控制。
version
:版本号resolved
:软件包位置integrity
:校验码执行脚本
npm run 「package.json中scripts字段的命令」 -- 「添加脚本后面的参数」
非-
开头的参数可以忽略--
而传递。e.g. npm run gulp runCss
等价于:npm run gulp -- runCss
执行的命令,优先找本地
node_modules/.bin
的命令,然后才找$PATH
的命令(暂时未找到绕过先找本地的方法)。
- 去
node_modules/.bin
路径检查命令是否存在,找到之后执行;- 找不到,就去环境变量
$PATH
里,检查命令是否存在,找到之后执行;- 还是找不到,自动下载一个临时的依赖包最新版本在一个临时目录,然后再运行命令,运行完之后删除,不污染全局环境。
包描述、说明文件。
name
仓库名。
组成:小写、无空格、字母数字下划线中划线。
version
版本号x.x.x
。
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]
更新version
。
dependencies
生产环境依赖。
npm install
)是从(项目根目录)最外层往(引用处)最里层安装,若本层已经有同名但不同版本的库文件夹,则往里层尝试安装;devDependencies
开发、测试依赖。
何时不被安装:
- 项目不会安装依赖库的
devDependencies
。
NODE_ENV
值为production
时,项目不会安装自己的devDependencies
。
export NODE_ENV=production;
。npm install --production
不会安装自己的devDependencies
。
main
代码入口,默认:index.js
。
引用仓库的代码入口,不在引用链路内的仓库文件最终不会被使用到。
其他非官方的代码入口(若不存在则退回main
):
module
:ES6 Moduleunpkg
:<script>
引用(针对unpkg.com)jsdelivr
:<script>
引用(针对www.jsdelivr.com)scripts
可执行脚本,用npm run 脚本名
执行。
pre脚本名
会在执行脚本名
之前自动执行。files
将仓库作为依赖项安装时要包含的路径、文件的数组。
bin
{ 新增的命令: 对应的可执行文件路径 }
engines
该仓库在哪个版本的Node.js(、npm、yarn、等)上运行。
browserslist
支持哪些浏览器(及其版本)。
description
描述,也作为在npm官网被搜索的内容。
repository
仓库远程版本控制,可以是github等。
e.g.
```json "repository": { "type": "git", "url": "git@github.com:用户名/仓库名.git" } // 针对Monorepo: "repository": { "type": "git", "url": "https://github.com/用户名/仓库名.git", "directory": "packages/文件夹名" } ```
keywords
在npm官网被搜索的关键字。
author
仓库作者。
contributors
仓库贡献者。
license
证书。
homepage
主页。
bugs
链接到软件包的问题跟踪器,最常用的是GitHub的issues页面。
private
设置为true
,则无法npm publish
,用于避免不小心公开项目。
命令特有的属性
某些命令特有的(如:ESLint的eslintConfig
、Babel的babel
、等),可以在相应的命令/项目文档中找到如何使用它们。
其他
包的制作-使用
制作:
按照CommonJS规范编写代码。
使用:
在Node.js环境下使用(require
)
如:
vue-cli
。
在浏览器环境下使用
webpack
、browserify
、rollup
)打包成能够在浏览器运行的JS代码。如:
Vue.js
。 - 作用域的包
@scope/project-name
.npmrc
npm的配置文件,或全局修改:npm config set xxx=yyy
(npm config get xxx
查看.npmrc
->全局;npm config get xxx -g
查看全局)。
npm config ls -l
查看所有已有配置和默认配置。
package-lock
、registry
。;
或#
。项目中使用某个开源库时,要考虑它的License和文件大小(若使用webpack打包,则可以使用webpack-bundle-analyzer进行分析)。
很容易导致ts类型的冲突,需要额外处理全局类型问题。
npm aliases(npm:6.9.0+)
pnpm aliases也类似。
安装带别名的指定仓库:
npm install 「别名」@npm:「[<@scope>/]<pkg>[@<version>]」 --save
会写入package.json的依赖:
"dependencies": {
"「别名」": "npm:「[<@scope>/]<pkg>[@<version>]」"
}
使用时,根据引用名进入指定的版本中
发布一个新依赖包,包含引用指定版本的目标依赖包,然后通过 新依赖包+相对路径 引用
安装依赖(
npm install
)是从(项目根目录)最外层往(引用处)最里层安装,若本层已经有同名但不同版本的库文件夹,则往里层尝试安装。
"「新依赖包」/node_modules/「目标依赖包」"
(若没有不同版本的同个依赖包,则路径错误)
(仅适合全新项目)Monorepo
各子项目中的依赖互相隔离。
一个模块就是一个Node.js文件。
主模块:通过命令行参数传递给Node.js以启动程序的模块,负责调度组成整个程序的其它模块完成工作。如:
node 文件名
、package.json
的main
。
概述
执行阶段(运行时)进行模块加载:确定模块的依赖关系、输入和输出的变量。
因为本身就是动态引入,module.exports
和require
能够在任何位置使用(包括块级作用域)。
有自己单独作用域,不污染全局作用域,必须module.exports
才能输出给其他模块。
不推荐
window.属性
。
模块的加载逻辑:
exports
、require
、module
三个全局变量。执行模块的源码:
require
第一次加载某模块,执行整个脚本,在内存生成一个缓存对象:
{ id: '...', exports: {...}, loaded: true/false, ... }
require(b)
则进入b模块代码中执行,执行完毕后再回到a模块继续向下执行。无论加载多少次,仅在第一次加载时运行,之后再被引用则直接返回已导出内容。
除非手动清除系统缓存。
「循环加载」(circular dependency)
引用之前已经被引用过的模块b,会直接返回模块b已导出的内容,而不会再进入模块b内执行。
exports
值输出至缓存,以供其他模块require
获取(或「循环加载」时,部分已经执行产生的exports
供其他模块引用)。require
的模块没有exports
,则仅执行一遍模块代码,返回{}
。require(a模块)
返回内存中a模块的module.exports
指向的值(值传递),require
的内容可以重新赋值和属性改写(重新赋值将不再使用模块引用;属性改写会改变a模块的缓存值,所有require(a模块)
都会共享)。
require
只是使用缓存起来的内容。已经引用模块后,被引用的模块内部再次修改导出内容,不会影响已经引用过的值(修改属性还是会影响)。e.g.
```javascript var a = 'a1' module.exports.a = a // 此时引用则获得`a1`的复制 setTimeout(() => { a = 'a2' // 不会改变a的输出。此时引用则还是获得`a1`的复制 }, 500) module.exports.b = 'b1' // 此时引用获得`b1`的复制 setTimeout(() => { module.exports.b = 'b2' // 重新输出b。此时引用则获得`b2`的复制 }, 500) ```
CommonJS是一个单对象输出、单对象加载的模型:
module.exports
上,然后暴露给外界。require
加载别的模块,require
的返回值就是模块暴露的对象。module.exports
模块提供使用的exports
值。
允许用两种方式为导出赋值:
module.exports = 值
module.exports.属性 = 值
exports
指向module.exports
若
exports = 值
或module.exports = 值
则切断了exports
与module.exports
的连接,exports
将没有导出效果(导出的永远是module.exports
)。e.g.
console.log(module.exports, exports, module.exports=== exports) // => {} {} true exports = {} // 或 module.exports = {} console.log(module.exports, exports, module.exports === exports) // => {} {} false
require(X)
加载模块。读取并执行一个JS文件(.js
后缀可以省略),返回该模块的exports
值(没有导出内容则为{}
)。
- 被引入的内容就可以被各种解构。
- 注意文件名大小写,可能导致引用路径失败(针对:开发时的系统大小写不敏感,上线或其他机器运行时系统大小写又敏感的情况)。
module
当前模块对象。拥有以下属性:
module.id
:模块的识别符,通常是带有绝对路径的模块文件名。module.filename
:模块的文件名,带有绝对路径。module.loaded
:返回一个布尔值,表示模块是否已经完成加载。module.parent
:返回一个对象,表示调用该模块的模块。module.children
:返回一个数组,表示该模块要用到的其他模块。module.exports
:表示模块对外输出的值。module.paths
:返回一个数组,模块文件默认搜索目录(某某/node_modules/
)。所有模块都是Node.js内部
Module
构建函数的实例。
V8引擎解析应用程序输入的JS脚本。
V8处理JS,除了 正则表达式、JSON的处理 之外,对于大部分操作都很快。
- 使用正则表达式时注意ReDoS(Regular expression Denial of Service,正则表达式拒绝服务攻击)风险。
JSON.parse/stringify
随着输入参数线性增加执行时间。
解析后的代码,能够与操作系统进行交互。
代码 -> Node.js内置模块 -> V8(internalBinding) -> C/C++ -> 操作系统
JS本身的
throw-try-catch
异常处理机制并不会导致内存泄漏,也不会让程序的执行结果出乎意料,但Node.js并不是存粹的JS。Node.js里大量的API内部是由C/C++实现,因此Node.js程序的运行过程中,代码执行路径穿梭于JS引擎内部和外部,而JS的异常抛出机制可能会打断正常的代码执行流程,导致C/C++部分的代码表现异常,进而导致内存泄漏等问题。
单线程
尽量不用同步方法,尽量不阻塞进程
高开销的同步方法: 1. `crypto`加密 1. `crypto.randomBytes`(同步版本) 2. `crypto.randomFillSync` 3. `crypto.pbkdf2Sync` - 同时你应当非常小心对加密和解密给予大数据输入的情况。 2. `zlib`压缩 1. `zlib.inflateSync` 2. `zlib.deflateSync` 3. `fs`文件系统 4. `child_process`子进程 1. `child_process.spawnSync` 2. `child_process.execSync` 3. `child_process.execFileSync`
不为单个客户端连接创建一个新的线程,而仅仅使用单一线程支持所有客户端连接。通过非阻塞I/O和事件驱动机制,让Node.js程序宏观上并行。
事件循环不同于许多其他语言的模型,其它语言创建额外线程来处理并发工作:Java、PHP或.NET等服务器语言,会为每一个客户端连接创建一个新的线程或使用协程。
非阻塞I/O
回调函数异步执行,通过事件循环检查已完成的I/O进行依次处理。
- 阻塞I/O、非阻塞I/O的区别:系统接收输入再到输出期间,能不能接收其他输入。
- I/O主要指由libuv支持的,与系统磁盘和网络之间的交互。
大多数Node.js核心API所提供的异步方法都遵从惯例:错误信息优先的回调模式(Error-first Callback,第一个参数是错误信息,若不报错则其值为
null
)。
EventEmitter
类事件函数不属于此范畴。
事件驱动
用事件驱动(事件循环)来完成服务器的任务调度。
- Node.js开发应用程序:不善于计算,善于I/O(任务调度)。如:长连接的实时交互应用程序。
- Node.js服务器:没有根目录概念,没有web容器。URL通过顶层路由设计,呈递静态文件。
- 事件循环逻辑和触发JS时机都是由系统(C/C++)控制,在需要时才开启某个具体js调用栈。
只有打通和后端技术的桥梁、实现互联互通,Node.js才能在公司业务中有更长远的发展。
require
引入)核心模块/内置模块 定义在源代码的lib/文件。同名加载时,核心模块优先级高于路径加载或自定义模块。
http
:HTTP请求相关API
为了支持所有可能的HTTP应用程序,Node.js的HTTP API都是非常底层的。
它仅进行流处理和消息解析。它将消息解析为消息头和消息主体,但不会解析具体的消息头或消息主体。
e.g.
1. Node.js原生处理POST/GET请求 ```javascript const http = require("http"); http .createServer((req, res) => { let body = ""; req.on("data", (chunk) => { body += chunk.toString(); }); req.on("end", () => { // 接受完成 const text = `收到${req.method}请求\nurl是:${req.url}${req.method === "POST" ? `\nbody是: ${body}` : ""}\n`; console.log(text); res.end(text); }); }) .listen(3000, "0.0.0.0"); ``` 2. Node.js原生发起POST请求 ```javascript const http = require("http"); const req = http.request( "路径", { method: "POST", headers: { "Content-Type": "application/json; charset=UTF-8", }, }, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (chunk) => { body += chunk || ""; }); res.on("end", () => { // 响应接受完成 console.log(body); // body是GET/POST的响应内容 }); } ); req.on("error", (e) => { console.warn(e); }); req.write(JSON.stringify({ a: "发起的请求body" })); // 保证:发起请求的body和请求头匹配 req.end(); ``` 3. Node.js原生发起GET请求 ```javascript const http = require("http"); const req = http.request( "路径", { method: "GET", }, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (chunk) => { body += chunk || ""; }); res.on("end", () => { // 响应接受完成 console.log(body); // body是GET/POST的响应内容 }); } ); req.on("error", (e) => { console.warn(e); }); req.end(); ```
http2
https
fs
:以标准POSIX函数为模型的方式与文件系统进行交互
提供版本:异步、同步(+Sync
)、基于Promise(require("fs").promises
)。其他模块也类似。
e.g.
```javascript // 异步(结果在回调;若出错,则回调函数第一个参数不为null或undefined) require('fs').rename('before.json', 'after.json', err => { if (err) { return console.error(err) } //完成 }) // 同步(阻塞线程,直到文件操作结束。结果在执行语句返回。需要try-catch等方式处理错误) try { require('fs').renameSync('before.json', 'after.json') //完成 } catch (err) { console.error(err) } // Promise require("fs").promises .rename("before.json", "after.json") .then((data) => { console.log('succeeded', data); }) .catch((error) => { console.error('failed' ,error); }); ```
尽量选择使用流读写文件的内容
(不使用流的)读操作都会在返回数据之前将文件的全部内容读取到内存中,这意味着大文件会对内存的消耗和程序执行的速度产生重大的影响;(不使用流的)写操作都是在将全部内容写入文件之后才会将控制权返回给程序(在异步的版本中,这意味着执行回调)。
导出文件需求:
后端直接返回文件,前端请求下载文件(get请求)
events
:事件触发器
EventEmitter
类:require('events')
EventEmitter
类的实例。EventEmitter
以注册的顺序同步地调用所有监听器。path
:处理文件路径
e.g. 当前文件所在目录的相对位置:
require('path').resolve(__dirname, '../../xx/xxx.txt')
stream
:数据流
流是一种以高效的方式处理读/写文件、网络通信、或任何类型的端到端的信息交换。
e.g. 在传统的方式中,当告诉程序读取文件或通信时,这会将文件或信息从头到尾读入内存,然后进行处理。若使用流,则可以逐个片段地读取并处理(而无需全部保存在内存中),能够一边读取一边处理更加高效。
EventEmitter
类的实例。fs
、http
、process
等都有流操作方式),除非要创建新类型的流实例,否则极少需要直接使用stream
。stream不需要手动创建缓冲区(buffer),在Node.js的流中将会自动创建。
readline
:用于一次一行地读取可读流中的数据Buffer是数据以二进制形式临时存放在内存中的物理映射,stream为搬运数据的传送带和加工器,有方向、状态、缓冲大小。
buffer
:缓冲区
Buffer
用于表示固定长度的字节序列。是JSUint8Array
的子类,并使用涵盖额外用例的方法对其进行扩展。
由于Buffer需要处理的是大量的二进制数据,假如用一点就向系统去申请,则会造成频繁的向系统申请内存调用,所以Buffer所占用的内存不再由V8分配,而是在Node.js的C++层面完成申请,在JavaScript中进行内存分配。 因此,这部分内存我们称之为堆外内存。
string_decoder
:字符串解码器
提供了用于将Buffer
对象解码为字符串(以保留编码的多字节UTF-8和UTF-16字符的方式)的API。
os
:提供了与操作系统相关的实用方法和属性util
:提供常用函数的集合,用于弥补核心JavaScript的功能过于精简的不足process
:进程
require("node:process") === process
child_process
:衍生子进程
衍生的Node.js子进程独立于父进程,但两者之间建立的IPC通信通道除外。每个进程都有自己的内存,带有自己的V8实例。
由于需要额外的资源分配,因此不建议衍生大量的Node.js子进程。
cluster
:集群(运行多个Node.js实例)
创建共享服务器端口的子进程。为了充分利用多核系统,有时需要启用一组Node.js进程去处理负载任务。
worker_threads
:工作线程(单个Node.js实例中并行运行多个应用程序线程)crypto
:加密
OpenSSL的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。
zlib
:提供Gzip、Deflate/Inflate、Brotli
的压缩功能
压缩和解压缩是围绕
stream
流API构建的。
dgram
:数据报
对UDP datagram sockets(UDP的数据报套接字)的一层封装。
net
:用于创建基于流的TCP或IPC的服务器(net.createServer
)与客户端(net.createConnection
)dns
:解析域名url
:解析URL。querystring
:解析和格式化URL查询字符串tls
:实现安全传输层(TLS)及安全套接层(SSL)协议,建立在OpenSSL的基础上module
:模块
提供几个对象属性。
timers
Node.js中的定时器函数实现了与网络浏览器提供的定时器API类似的API,但使用的是围绕Node.js事件循环构建的不同的内部实现。
require('node:timers')
require('node:timers/promises')
。perf_hooks
:性能钩子
实现w3c的Performance API的子类。
assert
:断言repl
:REPL(交互式解释器)
终端使用Node.js的REPL:
_
打印最后一次操作结果(不执行语句、没有副作用)。
.help
、.editor
、.break
、.clear
、.load
、.save
、.exit
可以直接使用系统模块,不需要require
引入。
e.g. REPL中
http === require('http')
。
v8
:V8的apivm
:提供V8虚拟机上下文中进行编译和运行代码console
控制台
require("node:console").Console === console.Console
inspector
:与V8调试器交互tty
:终端
在大多数情况下,没有必要或不可能直接使用此模块。
C++相关
Experimental功能
wasi
:实现WebAssembly系统接口规范diagnostics_channel
:诊断通道trace_events
:跟踪事件async_hooks
:异步钩子去跟踪异步资源Corepack
:管理包管理器的版本的工具require('crypto').webcrypto
:实现Web Crypto API规范require('stream').web
:网络流test
:测试弃用
domain
punycode
Node.js的全局对象global
是所有全局变量的宿主。
仅在模块内有效
require
exports
module
__filename
:当前正在执行的脚本所在文件夹的绝对路径+文件名。__dirname
:当前正在执行的脚本所在文件夹的绝对路径。process
:描述当前Node.js进程状态的对象,提供了一个与操作系统的简单接口。
process.cwd()
:运行node命令时所在文件夹的绝对路径。Buffer
:二进制数据流。
虽然
Buffer
在全局作用域内可用,但仍然建议通过import/require
语句显式地引用它:const { Buffer } = require('node:buffer')
。
可以将buffer视为整数数组:数组的每一项都是整数,并代表一个数据字节。
setImmediate/clearImmediate
类似于浏览器的全局对象window
所包含的全局变量
setTimeout/clearTimeout
、setInterval/clearInterval
console
URL
、URLSearchParams
Error
若在程序执行过程中引发了未捕获的异常或未捕获的失败Promise实例,则程序将崩溃。捕获未捕获的异常:
捕获未捕获的异常
process.on('uncaughtException', err => { // 执行逻辑 })
捕获未捕获的失败Promise实例
process.on('unhandledRejection', err => { // 执行逻辑 })
debugger
node inspect 脚本.js
:命令行调试node --inspect 脚本.js
:与Chrome配合调试(默认:--inspect=127.0.0.1:9229
。开启远程调试:--inspect=「公共IP或0.0.0.0」:9229
)
有时因为运行的代码,就算退出了程序也无法关闭占用inspect的9229(默认)端口。需要手动杀死占用端口的进程,e.g.
lsof -i :9229
然后kill -9 「PID」
。
--debug-brk
:直接从第一行代码开始进行断点调试。Blob
Event
、EventTarget
TextDecoder
、TextEncoder
WebAssembly
AbortController
、AbortSignal
BroadcastChannel
queueMicrotask
将微任务放入队列以便调用回调。
MessageChannel
、MessageEvent
、MessagePort
performance
structuredClone
DOMException
调试方法:
console
等。通过Chrome的 <chrome://inspect/#devices>,监听Node.js程序运行node --inspect 文件
,可以使用debugger
等进行断点调试。
调试指南。
服务端开发注意点:
相对于客户端,服务端要处理大量并发的请求。
需要学习服务端的各种 高并发、数据库IO 解决方案。前端处理客户端问题无法接触到这些,需要重新踩服务端的坑。
虽然Node.js是单线程,但是各种异步的操作(如:数据库IO等)需要按照服务端的技术解决方案处理。
基本数据库的使用,以及如何在Node.js中使用。
如:MySQL、Redis、MongoDB、等。
异常处理、错误报警
对各种IO要进行异常处理(如:try-catch
包裹所有IO代码),并需要把错误上报(打日志console
或借助第三方监控告警)。
与浏览器JS的区别
除了全局变量、提供的模块、模块系统、API不同之外,在Node.js中,可以控制运行环境:除非构建的是任何人都可以在任何地方部署的开源应用程序,否则开发者知道会在哪个版本的Node.js上运行该应用程序。与浏览器环境(无法选择访客会使用的浏览器)相比起来,这非常方便。
Node.js运行环境退出(命令行执行完毕后自动退出):
代码运行完毕。包括:执行队列、任务队列、等待加入任务队列的其他线程任务,全都执行完毕,当不会有新的指令需要执行时,就自动退出Node.js的进程。e.g. 监听系统端口 或 setTimeout
还未触发,意味着还有事件需要待执行。
.end()
)CLI命令行(man node
)
node [options] [V8 options] [<program-entry-point> | -e "script" | -] [--] [arguments]
node inspect [<program-entry-point> | -e "script" | <host>:<port>] …
node --v8-options
-
:标准输入的别名。 类似于在其他命令行工具中使用 -,这意味着脚本是从标准输入读取的,其余的选项将传给该脚本。--
:指示 node 选项的结束。 将其余参数传给脚本。 如果在此之前没有提供脚本文件名或评估/打印脚本,则下一个参数用作脚本文件名。
node -e "「js代码文本」"
:eval执行字符串;node -p "「js代码文本」"
:eval执行字符串(-e
)并打印。
undefined
的,可能会把这个属性去除。抓包Node.js发起的http/https请求
或 代码设置Node.js发起请求时通过代理转发
e.g.
1. Node.js的`host`和`port`参数 ```javascript const http = require("http"); const req = http.request( { port: "8899", // 代理端口 host: "127.0.0.1", // 代理地址 path: "http://127.0.0.1:8080/", // 访问地址 method: "POST", headers: { "Content-Type": "application/json; charset=UTF-8", }, }, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (chunk) => { body += chunk || ""; }); res.on("end", () => { console.log(body); // body是POST请求后返回的响应body // 返回接受完成 }); } ); req.on("error", (e) => { console.warn(e); }); req.write(JSON.stringify({ a: "发起的请求body" })); // 保证:发起请求的body和请求头匹配 req.end(); ``` 2. [axios](https://github.com/axios/axios)的`proxy`参数 3. [request](https://github.com/request/request)的`proxy`参数
关键点:级联 + 通过上下文(ctx)在中间件间传递数据 + ctx.body的值为HTTP响应数据。
级联(Cascading):中间件按顺序执行,随着第二个参数next
执行进入执行栈,所有中间件运行完毕后自动返回响应
为了能够更好的链式调用中间件,要使用
await next()
或return next()
的方式,否则虽然会next
进入下一个中间件,但下一个中间件的异步代码会导致请求先返回之后再处理异步后代码。
const Koa = require('koa')
const app = new Koa()
// 按照①②③④⑤⑥执行后输出
app.use(async (ctx, next) => {
// ①
await next()
// ⑥
})
app.use((ctx, next) => {
// ②
return next().then(() => {
// ⑤
})
})
app.use((ctx, next) => {
// ③
return next()
})
app.use(async ctx => {
// ④
})
app.use(async ctx => {
// 前一个中间件没有执行`next`,因此后面的中间件不再被执行
})
app.listen(3000)
中间件/拦截器的流程:从上到下,执行中间件,直到抵达路由匹配到的中间件为止,不再继续向下执行(若所有都不匹配,则执行兜底中间件)
const app = new Koa()
实例
app.context
上下文,可以添加新值
app.env
获取NODE_ENV
的值(默认:'development'
)
app.use(async (上下文, next) => {}))
使用中间件
app.callback()
返回一个函数,用于http.createServer()
的第一个参数
app.listen(数字)
创建并返回HTTP服务器
app.on('error', (err, ctx) => {})
错误处理,中间件产生的 未捕获的 同步错误(或同步ctx.throw
)都会捕获到这里
未捕获的异步错误用
process.on('uncaughtException', err => {})
捕获;未捕获的失败Promise实例用process.on('unhandledRejection', err => {})
捕获。
app.keys =
设置签名的 Cookie 密钥
其他
app.maxIpsCount
从代理 ip 消息头读取的最大ip数(默认:0
,表示无限)
app.middleware
所有用到的中间件的引用(数组)。
app.proxy =
若设置为true
,则header fields将被信任
app.proxyIpHeader
代理 ip 消息头(默认:'X-Forwarded-For'
)
app.subdomainOffset
.subdomains
忽略的偏移量(默认:2
)
app.emit(事件名[, ...args])
发起一个EventEmitter
事件。
app.request
app.response
上下文(context)
app.context
、中间件的第一个参数。
每一个请求都将创建一个新的上下文(来自app.context
),并在中间件间引用传递和新赋值。
对上下文的写入和读取
写入:
app.context.新属性 = 值
中间件写入
app.use((ctx) => {
ctx.新属性 = 值
})
读取:
app.use((ctx) => {
console.log(ctx.新属性)
})
HTTP响应的内容:所有中间件执行结束之后的ctx.body
值
.request
Koa的Request
.header
=== .request.header
=== .headers
=== .request.headers
.header=
=== .request.header=
=== .headers=
=== .request.headers=
.method
=== .request.method
.method=
=== .request.method=
.request.length
.url
=== .request.url
.url=
=== .request.url=
.originalUrl
=== .request.originalUrl
.origin
=== .request.origin
.href
=== .request.href
.path
=== .request.path
.path=
=== .request.path=
.query
=== .request.query
.query=
=== .request.query=
.querystring
=== .request.querystring
.querystring=
=== .request.querystring=
.request.search
.request.search=
.host
=== .request.host
.hostname
=== .request.hostname
.request.URL
.request.type
.request.charset
.fresh
=== .request.fresh
.stale
=== .request.stale
.socket
=== .request.socket
.protocol
=== .request.protocol
.secure
=== .request.secure
.ip
=== .request.ip
.ips
=== .request.ips
.subdomains
=== .request.subdomains
.is()
=== .request.is()
.accepts()
=== .request.accepts()
.acceptsEncodings()
=== .request.acceptsEncodings()
.acceptsCharsets()
=== .request.acceptsCharsets()
.acceptsLanguages()
=== .request.acceptsLanguages()
.request.idempotent
.get()
=== .request.get()
.response
Koa的Response
.response.header
=== .response.headers
.body
=== .response.body
.body=
=== .response.body=
.response.socket
.status
=== .response.status
.status=
=== .response.status=
.message
=== .response.message
.message=
=== .response.message=
.length
=== .response.length
.length=
=== .response.length=
.type
=== .response.type
.type=
=== .response.type=
.headerSent
=== .response.headerSent
.redirect()
=== .response.redirect()
.attachment()
=== .response.attachment()
.response.get()
.set()
=== .response.set()
.append()
=== .response.append()
.remove()
=== .response.remove()
.lastModified
≈ .response.lastModified
.lastModified=
=== .response.lastModified=
.etag
≈ .response.etag
.etag=
=== .response.etag=
.response.is()
.response.vary()
.response.flushHeaders()
.state
推荐的命名空间,用于通过中间件传递信息。
// 前面的中间件设置
ctx.state.属性1 = 值
// 后面的中间获得
ctx.state.属性1
.app
应用程序实例引用(const app = new Koa()
)。
.cookies
使用cookies模块。
.cookies.get(名[, options])
.cookies.set(名[, 值 [, options]])
.throw([状态[, 信息[, properties]]])
抛出错误(可以被try-catch
捕获处理)
.assert(值[, status[, 信息[, properties]]])
当值
为false
时抛出一个类似.throw
的错误。与Node.js的assert()
方法类似.
e.g. ctx.assert(ctx.state.user, 401, 'User not found. Please login!');
.req
Node.js的Request
.res
Node.js的Response
绕过Koa的Response
处理是不被支持的. 应避免使用以下Node.js属性:
res.statusCode
res.writeHead()
res.write()
res.end()
.respond
是否绕过Koa的Response
。
调试模式
运行前添加环境变量:DEBUG=koa*
回调函数的参数数量:
4个参数是错误处理中间件
错误处理中间件,需要回调函数固定传4个参数(Error-first)。
无论是哪种类型的中间件,都是按代码顺序依次执行。
express 与 koa 对比
区别 | express | koa |
---|---|---|
中间件模型 | 线性模型(没有 |
洋葱模型(级联) |
异步方式 | 基于回调函数 | 基于async/await |
捕获错误 | Error-First模式(中间件的回调函数若写4个参数则固定作为错误处理中间件) | 使用try/catch的方式 |
响应机制 | 调用API后立刻响应 | 所有中间件执行完之后才响应 |
集成成度 | 集成度高,自带部分中间件 | 集成度低,没有捆绑任何中间件 |
后台运行、进程管理(自动重启、永保活动状态、不停机重新加载,显示进程信息,配置进程处理条件)、多进程运行、负载均衡、处理log输出。
pm2
+
start
、stop
、restart
、startOrRestart
、delete
、reload
+ 执行文件、进程名/进程id/all、配置脚本(.js、.json、.yml)
start 执行文件或配置脚本
-- 「参数,已空格分隔」
传递进执行脚本。只能用在命令行最后,--
之后的所有内容都将当做是参数。
--node-args="「参数」"
相同:
--interpreter-args="「参数」"
。
传递给Node.js的node
运行命令(不是传递进执行脚本),如:--inspect
。
--log 「out、error脚本路径」
--output 「out脚本路径」
--error 「error脚本路径」
-i 「进程数:max、-1、数字」
cluster模式。
--merge-logs
当多进程使用同一个进程名时,不用进程id区分log文件(同名的多进程写到同一个log文件)。
--watch
监听改动文件后重新执行指令(先kill再重启)
--ignore-watch="「文件或文件夹」"
--interpreter=bash/python/ruby/coffee/php/perl/node
需要配置exec_mode: fork
、exec_interpreter: 对应语言
。
--max-memory-restart 「数字」K/M/G
达到最大内存就自动重启应用。
--restart-delay 「数字」ms
--time
每行log前缀添加时间戳
--env 「环境名」
使用配置脚本中的env_「环境名」
的环境变量配置。不传则默认使用配置脚本中env
的环境变量配置。
e.g.
```javascript // 默认使用 "env": { "DEBUG": "this" }, // 传`--env xx`时使用 "env_xx": { "DEBUG": "that" } ```
--cron 「cron格式」
cron形式的自动重启配置
--no-daemon
不使用pm2自己的守护进程运行。
stop all或进程名或进程id或执行文件或配置脚本
restart all或进程名或进程id或执行文件或配置脚本
startOrRestart all或进程名或进程id或执行文件或配置脚本
delete all或进程名或进程id或执行文件或配置脚本
reload all或进程名或进程id或配置脚本
如果是在cluster mode,reload会依序升级重启每一个程序,达到zero downtime升级
start/stop/restart/startOrRestart/delete/reload 配置脚本 --only 「进程名」
仅对某一个进程进行操作。
若使用配置脚本,则命令行参数大部分会被忽略(除了部分参数,如:
--env
、--only
、等)。
ecosystem/init
初始化ecosystem.config.js
文件。
list/ls/l/status
monit
logs
「进程名」
--json
--format
reloadLogs
重载所有logs。
flush
清空所有logs。
describe 「进程id或进程名」
show 「进程id或进程名」
ping
判断pm2守护进程正在运行。
kill
杀掉pm2自己,包括所有:pm2程序、pm2运行的进程、等,然后重启pm2。
pm2卡死的时候使用。
系统重启或pm2自己的重启
重启信息记录在:
$HOME/.pm2/dump.pm2
。
startup
save
unstartup
resurrect
serve 「文件路径,默认:./」 「端口号,默认:8080」
静态服务。
install 「模块名」
pm2 install typescript
-> pm2 start app.ts --watch
update
更新在内存中运行的pm2。
先在全局更新
npm install pm2@latest -g
,然后再更新在内存中运行的pm2。
deploy
部署、发布。