package.json 中波浪号~ 异或号^ 是什么意思?
目录(12)
执行 npm install lodash 时,package.json 里写入的往往不是精确版本 4.17.21,而是 ^4.17.21 或 ~4.17.21。这两个前缀决定 npm 在后续 npm update、安装传递依赖时,允许解析到哪个版本区间。
要读懂它们,得先弄清版本号本身的语义,再看 npm 如何把前缀翻译成范围。
SemVer:版本号三段各代表什么
语义化版本(SemVer) 规定标准版本号为 主版本号.次版本号.修订号,即 X.Y.Z:
| 段位 | 英文名 | 何时递增 |
|---|---|---|
X | Major(主版本) | 做了不兼容的 API 修改 |
Y | Minor(次版本) | 做了向下兼容的功能新增 |
Z | Patch(修订) | 做了向下兼容的问题修正 |
几个容易混的点:
1.2.3才是严格的 SemVer;v1.2.3里的v只是常见前缀,规范本身不要求它。0.y.z处于初始开发阶段,API 不保证稳定。1.0.0才通常被视为公共 API 成型的分界。- 先行版本(如
1.2.3-beta.1)和构建元数据(如1.2.3+20240101)可以接在后面,但日常依赖里见得少,本文不展开。
package.json 写的是「范围」,不是钉死一个版本
dependencies 里的值是版本范围(range),不是单个版本字符串。npm 安装时用 node-semver 判断某个已发布版本是否落在范围内。
下面两个符号是最常用的简写,分别叫 Tilde Range(波浪号) 和 Caret Range(插入符,即 ^)。
波浪号 ~:尽量锁在小版本内
如果写全了三段版本,只允许**修订号(patch)变化;如果只写了两段或一段,则允许次版本号(minor)**变化。
记法:~ 比 ^ 更保守,变动空间更小。
主版本 ≥ 1
| 写法 | 等价范围 | 通俗理解 |
|---|---|---|
~1.2.3 | >=1.2.3 <1.3.0 | 锁定 1.2.x,补丁可升 |
~1.2 | >=1.2.0 <1.3.0 | 同上,省略修订号时补 0 |
~1 | >=1.0.0 <2.0.0 | 锁定 1.x,次版本可升 |
以 ~1.2.3 为例:
✅ 允许:1.2.3 1.2.4 1.2.99
❌ 拒绝:1.2.2 1.3.0 2.0.0
主版本为 0
0.x 阶段规则相同,只是数字小一号:
| 写法 | 等价范围 | 通俗理解 |
|---|---|---|
~0.2.3 | >=0.2.3 <0.3.0 | 锁定 0.2.x |
~0.2 | >=0.2.0 <0.3.0 | 同上 |
~0 | >=0.0.0 <1.0.0 | 锁定 0.x |
插入符 ^:允许兼容范围内的次版本升级
允许那些不改变从左到右第一个非零段位的更新。
换句话说:最左边那个非 0 的数字,就是「不能跨过去」的边界。
主版本 ≥ 1:次版本和修订号都能升
| 写法 | 等价范围 | 通俗理解 |
|---|---|---|
^1.2.3 | >=1.2.3 <2.0.0 | 1.x 内任意兼容版本 |
^1.2 | >=1.2.0 <2.0.0 | 同上 |
^1 | >=1.0.0 <2.0.0 | 同上 |
以 ^1.2.3 为例:
✅ 允许:1.2.3 1.3.0 1.9.9
❌ 拒绝:1.2.2 2.0.0
这也是 npm install 的默认行为(save-prefix 默认为 ^):在 major 不变的前提下自动跟上新功能和补丁。
主版本为 0:边界随段位收缩
0.x 时,第一个非零段位不再是 major,规则会「缩紧」:
| 写法 | 等价范围 | 锁定的段位 |
|---|---|---|
^0.2.3 | >=0.2.3 <0.3.0 | 次版本 0.2(等同 ~0.2.3) |
^0.0.3 | >=0.0.3 <0.0.4 | 修订号 0.0.3(只允许补丁) |
^0.1.2 | >=0.1.2 <0.2.0 | 次版本 0.1 |
最常见的误区
很多人以为 ^0.1.2 表示 >=0.1.2 <1.0.0,实际上它是 >=0.1.2 <0.2.0。
原因在于 SemVer 把 0.y.z 视为不稳定期,node-semver 也据此假定:0.2.x 到 0.3.0 之间可能存在破坏性变更,因此 ^ 不会帮你跨过 0.2。
^0.1.2
✅ 允许:0.1.2 0.1.9
❌ 拒绝:0.1.1 0.2.0 1.0.0 ← 0.2.0 和 1.0.0 都会被拒绝
~ 和 ^ 怎么选
| 场景 | 建议 | 原因 |
|---|---|---|
| 应用项目、日常库依赖 | ^(npm 默认) | 自动获得兼容范围内的修复和新功能 |
| 对补丁升级也要严格控制 | ~ | 连次版本升级都不接受 |
| 必须可复现的构建 | 精确版本 1.2.3 + lockfile | 范围只表达意图,真正锁定靠 package-lock.json / bun.lock |
0.x 的库 | 格外小心 ^ | 范围可能比你想的窄;若作者频繁 breaking,考虑钉死版本 |
无论写 ^ 还是 ~,lockfile 才是安装时的最终裁决者。版本前缀只影响「允许 npm 解析到多新的版本」,不改变「这次装的就是 lockfile 里那一版」的事实。
速查对照
把上面的规则收成一张表,遇到不熟悉的写法可以直接查:
| 前缀 | 示例 | 等价范围 | 允许升级到什么 |
|---|---|---|---|
~ | ~1.2.3 | >=1.2.3 <1.3.0 | 仅 1.2.x 补丁 |
~ | ~1 | >=1.0.0 <2.0.0 | 整个 1.x |
^ | ^1.2.3 | >=1.2.3 <2.0.0 | 整个 1.x |
^ | ^0.2.3 | >=0.2.3 <0.3.0 | 仅 0.2.x |
^ | ^0.0.3 | >=0.0.3 <0.0.4 | 仅 0.0.3 的补丁 |
不确定某个版本是否命中范围时,可以用 node-semver 的 CLI 验证:
npx semver -r "^1.2.3" 1.2.2 1.2.3 1.9.0 2.0.0
# 输出:1.2.3 1.9.0
相关文档
- 语义化版本 2.0.0(中文版) — 版本号三段含义的权威定义
- node-semver README — npm 实际使用的范围解析规则(
~、^、-、*等)