Deno 有什么过人之处
Posted on 五 21 8月 2026 in Tech
| Abstract | Deno 有什么过人之处 |
|---|---|
| Authors | Walter Fan |
| Category | Tech |
| Version | v1.0 |
| Updated | 2026-08-21 |
| License | CC-BY-NC-ND 4.0 |
写脚本这件事,我一直有个心理阴影。
十几年了,每次想用 Node 写一个小工具,流程都是这样的:npm init、装 TypeScript、配 tsconfig.json、再装 ts-node 或者 tsx、顺手加个 .eslintrc、.prettierrc,等这堆前戏折腾完,node_modules 已经胖成一个黑洞,而我要写的那个"读一个文件、发一个请求"的小脚本还没动一行。更别提哪天 npm install 一个不知名的包,它的某个五层深的传递依赖在 postinstall 里偷偷读了我的环境变量——这种事在 JS 世界不是段子,是真实发生过的供应链攻击。
Deno 想解决的就是这两件事:开箱即用的 TypeScript 工具链,和默认不信任任何代码的安全沙箱。它出道时的姿态很高,号称要"修正 Node 的设计错误",还带着一条"我们不要 npm"的执念,结果差点把自己饿死。直到 2024 年 10 月 Deno 2 认怂、全面拥抱 npm,它才算真正长大成人。
这篇文章想帮你回答一个实在的问题:Deno 到底强在哪、弱在哪,什么活该交给它,什么活千万别碰它。 面向的是写过 Node、了解 npm 的后端和工具链开发者。
- 它的杀手锏是"零配置 TypeScript + 默认安全 + 一体化工具链",不是性能。
- 它不是 Node 的替代品,而是特定场景下更省心的选择:脚本、CLI、边缘函数。
What:Deno 是什么,Deno 2 又改了什么
Deno 是 Ryan Dahl 搞的 JavaScript/TypeScript 运行时。有意思的是,Ryan Dahl 正是当年 Node.js 的作者——他做 Deno,本质上是在给自己十年前的作品打补丁。名字 Deno 就是把 Node 的音节倒过来念。
它和 Node 最本质的区别有三条:
- 原生跑 TypeScript:
deno run app.ts直接就跑,不需要任何编译步骤、不需要tsconfig.json、不需要ts-node。 - 默认没有任何权限:一个 Deno 程序,你不显式授权,它就读不了文件、连不了网、看不了环境变量。这一点后面单独讲,是它最大的差异化。
- 工具链全内置:格式化(
deno fmt)、静态检查(deno lint)、测试(deno test)、打包成单文件可执行程序(deno compile)、依赖管理(deno add/deno install)——一个deno二进制全包了,不用再拼 Prettier + ESLint + Jest + webpack 这一大套。
但 Deno 1.x 有个致命的傲慢:它想抛弃 npm,让大家改用 URL 导入(import ... from "https://...")。结果就是,你写业务代码一转身发现要用的库全在 npm 上,Deno 却不认。这个执念差点要了它的命。
Deno 2 的核心就一句话:认怂,全面兼容 Node 和 npm。 官方博客说得很直白——"你无法在没有 npm 兼容性的情况下做一个 JavaScript 运行时"。具体来说:
- 认识
package.json和node_modules,大部分基于 ESM 的 Node 项目可以零改动直接跑。 - 用
npm:前缀直接导入 npm 包:import express from "npm:express",无需 install 步骤。 - 用
node:前缀访问 Node 内置模块:import fs from "node:fs"。 - 补齐了
deno add、deno install、deno remove、deno outdated这些和 npm 手感一致的命令。官方数据:冷缓存下装依赖比 npm 快约 15%,热缓存下快约 90%。
再加一个它自己力推的 JSR(JavaScript Registry)——一个 TypeScript 优先、只收 ESM 的现代包仓库。作者直接发布 TS 源码不用先 tsc,还能自动从 JSDoc 生成文档,而且发布时会自动转成 npm 能用的格式,Node 和 Bun 也能消费。
Why:这几条长处为什么值钱
工具的价值不在于功能列表多长,而在于它替你省掉了哪些反复出现的痛。Deno 值钱的地方就三条。
1. 默认安全,是真的和别人不一样
这是 Deno 唯一一条别人抄不走的护城河。
Node 的模型是:你 npm install 的任何一个包,跑起来就拥有和你本人一样的全部权限——读你的 ~/.ssh、发网络请求、翻你的环境变量,全都不用问你。你信任的是那个直接依赖,但你其实是在无条件信任它背后成百上千个你从没听说过的传递依赖。
Deno 反过来:代码默认什么都干不了。 要读文件得 --allow-read,要联网得 --allow-net,要看环境变量得 --allow-env。而且权限能收窄到具体资源:
# 只允许读 ./data 目录,只允许访问 API_KEY 这一个环境变量
deno run --allow-read=./data --allow-env=API_KEY app.ts
# 授予一大类权限,再挖掉敏感部分:能读任何地方,但 /etc 除外
deno run --allow-read --deny-read=/etc app.ts
对于要跑不可信输入、处理凭据的脚本和工具,这个模型是实打实的价值。Slack 就是冲着这条选的 Deno——他们的自动化平台要跑客户写的代码,"默认沙箱"是硬需求。
不过这里有个必须说清的边界:Deno 的沙箱不是万能的。 同一线程内、同一权限级别的代码之间没有任何隔离,eval、new Function、动态 import 都能在同权限下执行任意代码。还有一条容易踩的坑——初始静态模块图的加载不走权限检查:你 import 进来的本地文件、npm 包、远程 URL,运行时会直接加载,不需要 --allow-read。权限系统管的是"代码跑起来之后干什么",不是"哪些代码能被加载进来"。别把它当成能防住恶意依赖被引入的银弹。
2. TypeScript 零配置,省下的是心智负担
deno run app.ts 直接跑 TS,不装 ts-node、不写 tsconfig、不配 build 步骤。听起来是小事,但你要是维护过十几个内部小工具就懂——每个工具都得配一遍 TS 工具链,那种重复的琐碎才是真正磨人的。Deno 把这份琐碎直接抹掉了。
Plaid 迁了 100 个服务,据称靠 Deno 的工具链让迁移速度快了 5 倍,省的主要就是这类配置和工具拼装的功夫。
3. deno compile:把脚本变成一个能直接发出去的二进制
这是我个人最喜欢的一个功能。你写了个 TS 命令行工具,想发给一个连 Node 都没装的同事,怎么办?
# 编译成单文件可执行程序,权限被"焊死"进二进制里
deno compile --allow-net --output check_site server.ts
# 甚至能交叉编译出 Windows 版本,还能带图标
deno compile --allow-all --target x86_64-pc-windows-msvc --icon icon.ico -o runme.exe main.ts
产出的二进制自带运行时,目标机器不需要装 Deno。更妙的是:编译时指定的权限会被固化进二进制,运行的人既不会被弹权限提示,也永远无法让程序超出你编译时授予的权限范围。发工具给别人时,这既省心又安全。
How & Example:上手就这几步
不用装一大堆东西,一个二进制搞定。
# 安装(macOS / Linux)
curl -fsSL https://deno.land/install.sh | sh
# 直接跑一个 TS 文件,什么都不用配
deno run app.ts
# 加依赖:npm 和 JSR 混着来都行
deno add npm:zod # 一个 npm 包
deno add jsr:@std/assert # 一个 JSR 包
写一个"读环境变量、起 HTTP 服务"的小例子,能同时看到 npm 导入、JSR 导入和权限模型:
// server.ts
import express from "npm:express"; // 直接用 npm 包
import { serveDir } from "jsr:@std/http/file-server"; // JSR 标准库
const app = express();
app.get("/", (_req, res) => {
// 读环境变量需要显式授权,否则这里会报 PermissionDenied
res.send(`hello from ${Deno.env.get("APP_NAME")}`);
});
app.listen(8000);
跑起来:
# 只授予"联网"和"读 APP_NAME 这个环境变量"两项权限
deno run --allow-net --allow-env=APP_NAME server.ts
如果你漏了 --allow-env,程序会在读环境变量那一刻明确报错并告诉你缺哪个 flag——不是静默拿到 undefined,而是当面拦住你。这种"该有权限才有"的确定性,正是它和 Node 手感上最大的不同。
一个更实在的例子:遍历目录统计文件行数
光起个 HTTP 服务看不出 Deno 写脚本的爽感。换个更接地气的活——递归遍历一个目录,统计每种扩展名的文件数和总行数,这是我们平时想快速摸清一个陌生代码库大小时常干的事。
// cloc.ts —— 递归统计目录下各扩展名的文件数与行数
import { walk } from "jsr:@std/fs/walk"; // JSR 标准库,直接 import 就能用
const root = Deno.args[0] ?? "."; // 第一个命令行参数,默认当前目录
const stats = new Map<string, { files: number; lines: number }>();
// walk 递归遍历,只要文件,跳过常见的噪音目录
for await (const entry of walk(root, {
includeDirs: false,
skip: [/node_modules/, /\.git/],
})) {
const ext = entry.path.split(".").pop() ?? "(无扩展名)";
const text = await Deno.readTextFile(entry.path); // 读文件需要 --allow-read
const lines = text.split("\n").length;
const s = stats.get(ext) ?? { files: 0, lines: 0 };
s.files += 1;
s.lines += lines;
stats.set(ext, s);
}
// 按行数从多到少排序输出
const rows = [...stats.entries()].sort((a, b) => b[1].lines - a[1].lines);
console.log(`扩展名\t文件数\t行数`);
for (const [ext, s] of rows) {
console.log(`${ext}\t${s.files}\t${s.lines}`);
}
跑它,只给"读"这一项权限,而且收窄到你要统计的那个目录:
# 只允许读 ./src,其它目录一律碰不到
deno run --allow-read=./src cloc.ts ./src
这个小例子把 Deno 的几条卖点全串起来了:
import { walk } from "jsr:@std/fs/walk"—— JSR 标准库直接 import,不用先npm install fs-extra再配 TS。- 全程 TypeScript,
Deno.args、Deno.readTextFile都是内置 API,deno run cloc.ts一把跑,零配置。 --allow-read=./src—— 权限精确到目录。就算这脚本是从网上抄来的,它也读不到./src以外的任何文件,翻不了你的~/.ssh。- 想发给别人?
deno compile --allow-read -o cloc cloc.ts,产出一个自带运行时的二进制,对方不装 Deno 也能用。
同样的活在 Node 里,你得先 npm init、装 TS 工具链、(大概率还要)装个 glob 或 fast-glob、配好 build——等这些弄完,Deno 这边结果都跑出来了。这就是"写脚本更省心"最具体的样子。
Deno vs Bun:都想干掉 Node,路子却完全不同
聊 Deno 绕不开 Bun——这俩都是冲着"取代 Node"来的新运行时,但它们赌的是完全不同的东西。Bun 赌的是"快",Deno 赌的是"稳和安全"。
| 维度 | Deno 2 | Bun 1.x |
|---|---|---|
| JS 引擎 | V8(和 Node 同款) | JavaScriptCore + Zig |
| 冷启动 | 约二三十毫秒 | 个位数毫秒(最快) |
| HTTP 吞吐 | 中等 | 最高 |
| 装包速度 | 比 npm 快约 15%(冷)/90%(热) | 比 npm 快 10~25 倍,最猛 |
| 默认安全 | ✅ 默认拒绝一切(护城河) | ❌ 默认全开,和 Node 一样 |
| TypeScript | ✅ 原生零配置 | ✅ 原生零配置 |
| 一体化工具链 | ✅ fmt/lint/test/compile | ✅ 更全,还自带 bundler |
| 单文件二进制 | ✅ deno compile(权限可焊死) |
✅ bun build --compile |
| 边缘部署 | Deno Deploy / Netlify Edge | Cloudflare / Vercel 等 |
| npm 兼容 | 高(约 90%+) | 高(约 95%),原生模块支持更好 |
从这张表能读出一句话结论:
要极致的开发速度和跑分,选 Bun;要默认安全的沙箱,选 Deno。 其余的(原生 TS、一体化工具链、单文件二进制、npm 兼容),两家已经打得难分伯仲,趋同了。
具体到选择:
- 你在写要跑不可信代码、要处理凭据的脚本或工具——闭眼选 Deno,那个"默认什么都干不了"的沙箱是 Bun 给不了的。
- 你要的是 CI 装包快、本地跑测试快、serverless 冷启动省钱——选 Bun,它在"快"这个维度是碾压的。
- 你有一堆带原生模块(
sharp、node-gyp之类)的依赖——Bun 的兼容更稳,Deno 大概率当场翻车。
顺带说个八卦但重要的信号:2025 年底 Anthropic 收购了 Bun 背后的团队(Bun 是 Claude Code 的核心基础设施)。这意味着 Bun 有了更硬的金主和更清晰的商业绑定,选型时值得把"背后是谁、活不活得下去"也算进去——Deno 背后是 Deno Land Inc.,靠 Deno Deploy 这类服务变现。
短处:别被安利冲昏头
一篇只讲优点的文章是软文。Deno 有几条实打实的短板,必须说在前面。
1. 性能不是它的强项。 很多人以为新运行时就等于快,Deno 恰恰不是靠这个赢的。前面那张对比表已经说得很清楚——冷启动和 HTTP 吞吐都不如 Bun,真要拼裸速度它排不上号。(提醒一句:这类跑分数字随版本浮动很大,且一旦请求路径里接上数据库,运行时之间的差距通常会缩到 5% 以内,别拿合成基准当选型的唯一依据。)
2. npm 兼容是"高",不是"满"。 Deno 2 的 Node 兼容层已经相当完整,但仍有明确的黑名单:
- 原生插件(
.node的 C++ addon)不支持——这会直接卡死sharp(图像处理)、部分数据库驱动、bcrypt的 C++ 版(好在纯 JS 的bcryptjs能用)。 - 需要
node-gyp在安装时编译的包彻底跑不了。 cluster、child_process、worker_threads有一些边角 case 有缺口。- 依赖 Node 未公开内部实现的包可能出问题。
对 API 服务、CLI、工具链这类应用,兼容性通常够用;但迁移前,凡是带原生模块的依赖都必须先实测。
3. 生态和招人是硬伤。 论周下载量,Node 在亿级,Deno 只有百来万这个量级。AWS Lambda、Google Cloud Functions 这些平台对 Node 是一等公民,对 Deno 还是自定义运行时层。团队里"我们用了个非主流运行时"这句话,一旦上了线上事故复盘报告,是要挨批的。
应用场景与成功案例:什么活该交给它
结合它的长短处,Deno 真正的甜区其实很清晰,绝不是"取代 Node 做业务主服务器"。
| 场景 | 适合 Deno 吗 | 为什么 |
|---|---|---|
| 一次性脚本 / 内部 CLI 工具 | ✅ 非常适合 | 零配置 TS + 默认安全 + deno compile 发布,正中下怀 |
| 边缘函数(edge functions) | ✅ 非常适合 | Deno Deploy / Netlify Edge 底层就是 Deno,Web 标准 API 齐全 |
| 跑不可信代码的平台 | ✅ 非常适合 | 默认沙箱是刚需,Slack 就是这么选的 |
| 全新的 TypeScript 项目 | ✅ 可以考虑 | 想要零配置 TS 和一体化工具链 |
| 企业级生产主服务器 | ⚠️ 谨慎 | 生态、招人、云平台支持都是 Node 更稳 |
| 依赖大量原生模块的项目 | ❌ 别碰 | 原生 addon 和 node-gyp 直接不兼容 |
真实用例(均来自公开报道,非厂商内部数据我无法核实,仅供参考):
- Slack:自动化平台上跑客户代码,冲着默认安全沙箱选的 Deno。
- Netlify Edge Functions:底层运行时就是 Deno。
- Supabase Edge Functions:同样基于 Deno。
- Plaid:借 Deno 工具链把 100 个服务的迁移速度提了 5 倍。
一句话概括业界共识:Node 做生产业务服务器,Deno 做工具和边缘,Bun 做构建管线和部分新项目。 这不是骑墙,是三个工具确实各有各的位置。
最佳实践与常见陷阱
真要用起来,下面这几条是能帮你少走弯路的。
最佳实践:
- 权限从小往大加,别一上来就
-A。-A(--allow-all)等于把沙箱整个关掉,跟裸跑 Node 没区别,那你用 Deno 的意义就没了。先不给权限跑,缺什么报错就精确补什么。 --allow-net不写值就是放开整个网络,能收窄就收窄到具体域名。其他权限同理,能带值就带值。- 迁移老项目前,先把带原生模块的依赖挑出来单测。 别整个项目一把梭,撞上
sharp或node-gyp会当场翻车。 - 发工具给别人,优先
deno compile,把权限焊进二进制,对方既省事又出不了格。 - 新项目可以试试
deno.json+ import map + 显式npm:/jsr:指定,一个配置文件搞定依赖,比散落的npm:前缀干净。
常见陷阱:
- 误以为沙箱能防住恶意依赖被引入。 前面说过,静态模块图的加载不走权限检查。沙箱管的是运行时行为,不是"什么能被 import 进来"。
- 随手
-A图省事。 一旦养成习惯,Deno 最大的卖点就废了。 deno compile的权限是编译时定的,不是运行时。 忘了在 compile 命令上加--allow-write,产出的二进制运行时照样没写权限,还容易误以为是二进制的问题。- 拿裸跑分选型。 冷启动、吞吐这类数字随版本变动大,接上数据库后差距还会大幅缩小。选型要看场景,不看 Twitter 上的跑分截图。
- 指望原生模块能用。 凡是
.nodeaddon、node-gyp编译的包,先默认不支持,实测通过再说。
总结:Deno 的过人之处不在快,在省心和安全
Deno 最出名的一段历史,是它先高调宣称要"抛弃 npm、修正 Node 的错误",又在 Deno 2 里低头认怂、全面拥抱 npm。这段弯路反而让它想清楚了自己是谁:它不是来取代 Node 当业务主力的,它是那个让你写脚本、发工具、跑边缘函数时更省心、更安全的选择。
它的过人之处从来不是性能——论快有 Bun,论生态有 Node。它的过人之处是那三样别人学不来又抄不齐的组合:零配置 TypeScript、默认拒绝一切的安全沙箱、一个二进制搞定的工具链。 想清楚这三样对你的场景值不值,就知道该不该用它了。
选型这事,永远是看约束条件,不是看谁在热搜上喊得响。
全文思维导图
@startmindmap
<style>
mindmapDiagram {
node {
BackgroundColor #F8F9FA
RoundCorner 10
Padding 10
FontSize 13
}
:depth(0) {
BackgroundColor #1E3A5F
FontColor white
FontSize 18
FontStyle bold
}
:depth(1) {
FontSize 15
FontStyle bold
}
:depth(2) {
FontSize 13
}
}
</style>
* Deno 有什么过人之处
** What
*** 原生跑 TypeScript
*** 默认无权限
*** 工具链全内置
*** Deno 2 认怂拥抱 npm/node
*** JSR 现代包仓库
** 长处(Why)
*** 默认安全沙箱(护城河)
*** TS 零配置省心智
*** deno compile 单文件二进制
** vs Bun
*** Bun 赌快, Deno 赌安全
*** 拼跑分选 Bun
*** 要沙箱选 Deno
*** Anthropic 收购了 Bun
** 短处
*** 性能不如 Bun
*** npm 兼容高但不满
*** 原生模块/node-gyp 不支持
*** 生态与招人是硬伤
** 应用场景
*** 脚本 / CLI 工具
*** 边缘函数
*** 跑不可信代码
*** 别碰: 生产主服务器/原生模块
** 成功案例
*** Slack
*** Netlify / Supabase Edge
*** Plaid
** 最佳实践与陷阱
*** 权限从小往大加
*** 别随手 -A
*** compile 权限是编译时定
*** 别拿裸跑分选型
@endmindmap

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 欢迎在我的个人网站 https://www.fanyamin.com 访问原文并评论。