服务端5月31日 17:42
Deno 为什么能直接运行 TypeScript?类型检查怎么配?Deno 对 TypeScript 的支持不是“帮你装好了 ts-node”这么简单,而是把 TypeScript 文件作为一等公民纳入运行时流程。你可以直接运行 `.ts` 文件,也可以单独做类型检查、配置编译选项、缓存远程依赖类型。它适合写脚本、服务端 API、边缘函数和共享工具库,但仍然需要理解类型检查和运行性能之间的取舍。
## 追问
### Deno 运行 TypeScript 时到底发生了什么?
当你执行 `.ts` 文件时,Deno 会解析依赖图,把 TypeScript 转成 JavaScript 后交给 V8 执行,并把结果缓存起来。后续运行同一份代码时,如果依赖和配置没有变化,启动成本会低很多。取舍是首次运行或依赖变更时会有编译成本,所以大型项目不要把“直接运行”理解成完全没有构建开销。踩坑点是远程依赖版本不固定会导致缓存变化,建议在导入地址、npm specifier 或 lock 文件里锁住版本。
```bash
deno run src/main.ts
deno cache src/main.ts
deno run --reload src/main.ts
```
### deno check 和 deno run --check 有什么区别?
`deno check` 只做类型检查,不执行代码,适合放在 CI 或提交前检查里。`deno run --check` 会在运行前做类型检查,更适合本地调试关键入口。取舍是每次运行都检查会更稳,但反馈速度可能变慢;开发期可以按场景选择,CI 必须保留明确的类型检查步骤。常见坑是以为 `deno run` 默认总会拦住所有类型错误,实际项目最好显式运行 `deno check`,避免配置或版本差异造成误判。
```bash
deno check src/main.ts
deno run --check --allow-net src/main.ts
```
### Deno 还需要 tsconfig.json 吗?
很多简单项目不需要单独的 `tsconfig.json`,一个 `deno.json` 就能管理 tasks、imports、fmt、lint 和 compilerOptions。这样配置集中,脚本和服务端项目会清爽很多。边界是如果你和 Node.js、前端框架或 monorepo 共用 TypeScript 配置,仍然可能需要兼容既有 tsconfig。踩坑是直接复制 Node 项目的 tsconfig,里面的 `moduleResolution`、路径别名或类型声明未必适合 Deno,迁移时要逐项确认。
```json
{
"compilerOptions": {
"strict": true,
"lib": ["deno.ns", "dom", "dom.iterable"]
},
"imports": {
"@std/assert": "jsr:@std/assert@1"
}
}
```
### 第三方库的类型怎么处理?
Deno 导入的现代 ESM 模块通常自带类型,JSR 和不少 npm 包也能直接获得类型提示。遇到纯 JavaScript 或类型不完整的库,可以用声明文件补类型,或者换成类型更完整的依赖。取舍是补声明能快速推进业务,但长期看会增加维护成本,尤其是库升级后声明可能和真实行为不一致。踩坑点是 npm 包如果依赖 Node 专有 API,类型能过不代表运行能过,还要验证运行时兼容性。
```ts
import { assertEquals } from "jsr:@std/assert@1";
import express from "npm:express@4";
assertEquals(typeof express, "function");
```
### 严格类型会不会拖慢开发?
短期看,`strict: true` 会让你多处理空值、联合类型和外部输入校验,确实比随手写 `any` 慢。长期看,它能把很多线上问题提前变成编辑器和 CI 的提示,尤其适合 API 参数、配置文件和数据转换代码。取舍是原型验证阶段可以局部放宽,但核心模块不要长期依赖 `any`,否则 TypeScript 只剩语法高亮。常见坑是只定义接口不做运行时校验,外部 JSON 进来仍然可能是错的,必要时要配合 zod 这类校验库。
## 小结
Deno 的 TypeScript 体验好在默认路径短:写 `.ts`、跑 `deno check`、用 `deno task` 固化流程。真正稳定的项目还需要锁依赖、明确 compilerOptions,并把类型检查放进 CI;这样才能享受少配置,而不是把隐患藏到运行时。标签
Deno
Deno 是一个简单、现代且安全的 JavaScript 和 TypeScript 运行时环境,由 Node.js 的创始人 Ryan Dahl 开发,目标是解决 Node.js 的一些设计缺陷。Deno 于2020年正式发布,它内置了 V8 JavaScript 引擎和 Tokio 事件循环,提供了一系列默认的安全限制,并支持 TypeScript 的运行而无需额外的转译步骤。

服务端5月31日 17:42
Deno 内置工具怎么用?哪些场景能少装依赖?Deno 的一个明显特点是把格式化、检查、测试、文档、性能测试和编译都放进了 CLI。对小团队来说,这意味着项目刚创建时不用先决定 ESLint、Prettier、Jest、ts-node、打包器分别怎么配。它不是要替代所有前端工程工具,而是把 TypeScript 服务端、脚本和库开发中最常见的流程先收拢起来。
## 追问
### deno fmt 和 deno lint 能替代 Prettier、ESLint 吗?
在纯 Deno 项目里,大多数情况下可以直接用 `deno fmt` 和 `deno lint`,它们开箱即用,团队不用争论缩进、引号和基础规则。取舍是规则可配置空间没有 ESLint 那么大,如果项目有大量自定义 lint 规则、React 特定规则或历史代码风格,迁移时会有摩擦。边界也很清楚:Deno 工具更适合统一风格和抓常见问题,不适合承载复杂业务规范。常见坑是把 `deno fmt` 直接作用到整个仓库,结果格式化了不该改的生成文件,所以要在 `deno.json` 里排除目录。
```json
{
"fmt": { "exclude": ["dist", "coverage"] },
"lint": { "exclude": ["dist", "vendor"] }
}
```
```bash
deno fmt --check
deno lint
```
### deno test 适合测哪些代码?
`deno test` 适合测试工具函数、HTTP handler、Deno 服务端模块和不依赖浏览器 DOM 的逻辑。它支持权限参数,所以测试里如果要读文件或访问本地端口,需要显式声明,这能逼你看清测试到底碰了哪些资源。取舍是它不像 Jest 那样自带庞大的生态和快照习惯,迁移 React 组件测试时未必划算。踩坑点是异步测试忘记 `await` 或测试权限没写全,表现可能像代码错了,其实是运行命令不完整。
```ts
import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";
Deno.test("sum works", () => {
assertEquals(1 + 2, 3);
});
```
```bash
deno test --allow-read=./fixtures
```
### deno task 有什么实际价值?
`deno task` 类似 npm scripts,用来把常用命令写进 `deno.json`,让本地和 CI 使用同一套入口。它的价值不是功能多,而是减少“你本地怎么跑”的沟通成本。取舍是跨运行时项目仍然可能需要 pnpm、make 或 turborepo,Deno task 更适合 Deno 子项目内部统一命令。常见坑是把权限参数散落在文档里,最后 CI 忘加权限,最好统一写到 task 中。
```json
{
"tasks": {
"dev": "deno run --watch --allow-net --allow-read src/main.ts",
"check": "deno fmt --check && deno lint && deno test --allow-read"
}
}
```
```bash
deno task check
```
### deno compile 什么时候有用?
`deno compile` 可以把脚本打成单个可执行文件,适合内部 CLI、运维工具和发给非前端同事使用的小程序。好处是对方不需要先安装 Deno,也不用理解依赖缓存。边界是产物体积通常不小,且动态读取文件、远程导入和运行时权限需要提前想清楚。踩坑最多的是编译时没有把所需权限和目标平台写进去,结果本地能跑,换机器后访问网络或文件失败。
```bash
deno compile --allow-read --allow-net --output bin/tool src/cli.ts
```
### deno doc 和 deno bench 值得放进日常流程吗?
如果你在写共享库或内部 SDK,`deno doc` 很值得用,因为它能从类型和 JSDoc 直接生成 API 文档,减少文档和代码不一致。`deno bench` 适合比较关键函数的性能变化,但不应该拿一次本机结果做绝对结论。取舍是文档和基准测试都会增加维护成本,只有公共 API、核心算法或性能敏感路径才值得固定下来。踩坑是 benchmark 受机器负载影响很大,最好比较同一环境下的相对变化,而不是追求漂亮数字。
## 小结
Deno 内置工具最适合解决“项目启动阶段工具太多”的问题。先用 `fmt + lint + test + task` 建立基本质量线,需要分发 CLI 时再考虑 `compile`,需要维护库时再补 `doc` 和 `bench`,这样既能少装依赖,也不会把工具链用过头。服务端5月31日 17:42
Deno 和 Node.js 到底有什么区别?项目该怎么选?Deno 和 Node.js 都能运行 JavaScript,也都基于 V8,但它们解决问题的默认姿势不一样。Node.js 的优势是生态成熟、npm 包足够多、线上案例多;Deno 的优势是默认安全、原生 TypeScript、工具链内置,并且更贴近 Web 标准。真正选型时,不要只看“新不新”,要看项目依赖、团队经验、部署环境和安全边界。
## 追问
### Deno 和 Node.js 的模块管理差在哪?
Node.js 早期以 CommonJS 为主,现在也支持 ES Modules,所以老项目里经常同时看到 `require` 和 `import`。Deno 默认使用 ES Modules,可以直接从 URL、npm specifier 或 JSR 导入依赖,不强制依赖 `node_modules`。这种方式的取舍很明显:Deno 的依赖入口更透明,适合小工具和新项目;Node.js 的 npm 生态更大,遇到冷门 SDK 时更稳。踩坑点是 Deno 的远程 URL 一定要锁版本,否则上游变更可能让构建不可复现。
```ts
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
import chalk from "npm:chalk@5";
serve(() => new Response(chalk.green("hello from Deno")));
```
### Deno 默认安全是什么意思?
Node.js 程序默认可以读文件、访问网络、读取环境变量,权限通常靠容器、系统用户或部署平台隔离。Deno 反过来,脚本默认没有文件、网络和环境变量权限,运行时必须显式加 `--allow-read`、`--allow-net` 等参数。这个设计适合运行第三方脚本、CLI 插件或自动化任务,因为权限边界写在命令里。边界是后端服务最终仍要访问数据库、Redis 和配置,权限参数会变多,团队需要把它们固化到 `deno.json` 或部署脚本里。
```bash
deno run main.ts
deno run --allow-net=api.example.com --allow-read=./config main.ts
```
### TypeScript 支持是不是 Deno 一定更好?
Deno 可以直接运行 `.ts` 文件,不需要先安装 `typescript`、`ts-node` 或维护一套复杂构建配置。它对写脚本、边缘函数和小型服务很舒服,类型检查和运行命令都比较直接。取舍在于大型 Node.js 项目通常已有成熟的 tsconfig、构建缓存、路径别名和框架插件,迁到 Deno 未必省事。另一个常见坑是“能运行”不等于“已经类型检查”,实际 CI 里仍建议单独跑 `deno check`,避免为了启动速度跳过类型问题。
```bash
deno check src/main.ts
deno run --check src/main.ts
```
### 生态差距会影响真实项目吗?
会,而且是最现实的差距。Node.js 的 npm 包数量、企业 SDK、监控探针、ORM、框架插件都非常丰富,遇到支付、云厂商、旧系统集成时更容易找到现成方案。Deno 已经支持很多 npm 包,但不是所有包都能无缝运行,尤其是依赖 Node 原生模块、文件系统假设或构建脚本的包。项目如果强依赖复杂 npm 生态,Node.js 通常成本更低;如果是 API 服务、脚本平台、边缘函数或 TypeScript 优先的新工具,Deno 的开发体验更清爽。
### 线上项目应该怎么选?
已有 Node.js 项目不要为了追新整体迁移,除非你能明确量化安全、部署或工具链收益。新项目如果依赖 Express/NestJS、Prisma 复杂插件或大量 npm 中间件,Node.js 仍然是稳妥选择。Deno 适合权限边界清晰、依赖少、希望减少工具链配置的项目,例如内部 CLI、Webhook 服务、轻量 API 和自动化脚本。真正的踩坑不是选错运行时,而是没有把依赖锁定、权限参数、CI 检查和部署镜像写清楚。
## 小结
Node.js 像一座成熟城市,路网复杂但配套齐全;Deno 更像规划更现代的新区,默认规则更干净,但有些店还没开。选择时先列出项目必须依赖的包、运行权限、团队维护成本和部署平台支持,再决定运行时,比单纯比较性能或语法更靠谱。服务端5月31日 17:20
Deno 的 Worker 任务系统怎么用?适合哪些并行场景?Deno 里常说的任务系统,实际落到代码里主要是两件事:用 `deno task` 管理项目脚本,用 `Worker` 把耗时计算放到独立线程。原文讨论的是后台异步任务,更准确地说是 Worker 模型。Worker 有自己的执行上下文和内存空间,不能直接共享主线程变量,只能靠 `postMessage` 传数据。它适合 CPU 密集型或可拆分的批处理,不适合把普通 I/O 都丢进去;如果只是请求接口或读几个小文件,Worker 的创建和序列化成本可能比收益还高。
这个区别很重要,因为很多文章会把 “task” 和 “worker” 混着讲。面试或项目评审时,应该先说明你讨论的是哪一层:命令编排层的 `deno task`,还是运行时并行层的 Worker。前者解决“怎么启动和约束命令”,后者解决“怎么让重计算不堵住主线程”。两者都能提升工程体验,但解决的问题完全不同。
## 追问
### Deno 里怎么启动一个 Worker?
主线程用 `new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module" })` 创建 Worker,然后通过 `postMessage` 发消息。Worker 侧监听 `self.onmessage`,处理完再 `self.postMessage` 返回结果。这里的边界是消息会被结构化克隆,函数、类实例方法、部分复杂对象不能像普通引用一样传过去。踩坑点是路径最好用 `new URL(..., import.meta.url)`,不要依赖当前工作目录,否则测试和生产启动目录一变就找不到文件。如果 Worker 文件还需要读本地模块或资源,主进程启动时仍然要给对应权限。权限不是因为进入 Worker 就自动放开,Deno 仍然会按运行命令的授权范围执行,这是它和一些传统脚本环境不一样的地方。
```ts
// main.ts
const worker = new Worker(new URL("./worker.ts", import.meta.url).href, {
type: "module",
});
worker.onmessage = (event) => {
console.log(event.data);
worker.terminate();
};
worker.onerror = (event) => {
console.error(event.message);
worker.terminate();
};
worker.postMessage({ number: 21 });
```
```ts
// worker.ts
self.onmessage = (event) => {
const { number } = event.data;
self.postMessage({ result: number * 2 });
};
```
```bash
deno run --allow-read main.ts
```
### Worker 适合处理什么任务,不适合处理什么任务?
Worker 适合图像像素处理、加密哈希、大数组计算、日志离线分析、批量文本转换这类 CPU 时间明显的任务。它不适合非常短的小任务,因为创建线程、加载模块、传递消息都有固定成本。也不适合需要频繁共享状态的逻辑,主线程和 Worker 来回通信太密会把性能优势吃掉。取舍上可以先在主线程写清楚逻辑,再用实际耗时数据决定是否拆到 Worker,而不是一开始就把架构做复杂。还有一个判断标准是数据传输成本:如果每次都要把几十 MB 的对象来回复制,Worker 的收益会明显下降。能用 `ArrayBuffer` 等可转移对象时,应该优先考虑转移所有权,减少复制带来的内存压力。
```ts
// worker.ts
self.onmessage = (event) => {
const nums = event.data as number[];
const sum = nums.reduce((acc, n) => acc + n, 0);
self.postMessage(sum);
};
```
### 如何把 Worker 封装成 Promise,避免回调散在业务里?
真实项目里通常不会在业务函数里到处写 `onmessage` 和 `onerror`,而是封装一个 `runWorker`,让调用方像等待普通异步函数一样等待结果。这样错误处理、超时、终止 Worker 都可以集中维护。边界是每次调用都新建 Worker 会更简单但成本更高,适合低频任务;高频任务应该考虑 Worker 池。最容易踩的坑是成功或失败后忘记 `terminate()`,导致后台线程一直占着资源,CI 或长驻服务里会越来越慢。封装时还可以加超时控制,避免 Worker 卡死后 Promise 永远不返回。超时后要主动终止 Worker,并把任务标记为失败或进入重试队列,否则调用方会以为系统只是“还在处理”。
```ts
export function runWorker<T>(file: string, data: unknown): Promise<T> {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL(file, import.meta.url).href, { type: "module" });
worker.onmessage = (e) => { resolve(e.data as T); worker.terminate(); };
worker.onerror = (e) => { reject(e); worker.terminate(); };
worker.postMessage(data);
});
}
```
### 并行处理时为什么不能无限开 Worker?
Worker 不是越多越快,它会占用线程、内存和调度资源。CPU 密集型任务通常按机器核心数附近控制并发,比如 4 核机器开 4 个左右,再多可能只是上下文切换变多。批量文件处理还要考虑磁盘 I/O,开太多 Worker 可能把磁盘打满,反而让整体变慢。更稳妥的做法是做一个简单队列或 Worker 池,让任务排队进入固定数量的 Worker。Worker 池的实现也别一开始就追求复杂调度,先做到固定并发、先进先出、失败可返回就够了。等确实遇到长短任务混排、优先级或取消需求,再增加队列策略会更稳。
```ts
const concurrency = Number(Deno.env.get("WORKER_CONCURRENCY") ?? 4);
const chunks = [/* split big data here */];
for (let i = 0; i < chunks.length; i += concurrency) {
const batch = chunks.slice(i, i + concurrency);
await Promise.all(batch.map((chunk) => runWorker("./worker.ts", chunk)));
}
```
```bash
WORKER_CONCURRENCY=4 deno run --allow-read --allow-env main.ts
```
### Deno 的 deno task 和 Worker 是一回事吗?
不是。`deno task` 是项目脚本系统,类似 npm scripts,用来定义 `test`、`dev`、`build` 这类命令;Worker 是运行时代码里的并行执行机制。两者可以配合,例如用 `deno task process` 启动一个会创建 Worker 池的批处理程序。面试里如果把二者混为一谈,通常说明只看过标题没真正写过 Deno。项目里建议把常用命令写进 `deno.json`,把 Worker 并发、权限和入口文件固定下来,减少同事之间“我这里能跑”的差异。另外,`deno task` 里写权限时要尽量精确,比如只允许读输入目录、只允许写输出目录。把权限写进脚本后,团队成员运行同一个任务时环境更一致,也更容易在代码审查里发现权限扩大。
```json
{
"tasks": {
"process": "deno run --allow-read --allow-write --allow-env main.ts",
"test": "deno test --allow-read"
}
}
```
Deno 的 Worker 任务模型真正有价值的地方,是把重计算从主流程里拆出去,同时保留清晰的权限和启动命令。用之前先判断任务是否足够重、数据是否好切分、失败后是否能重试,比盲目并行更重要。服务端5月31日 17:20
Deno 的测试框架怎么用?异步、权限和覆盖率怎么处理?Deno 的测试框架是内置能力,不需要先装 Jest、Mocha 这类第三方 Runner。核心用法是用 `Deno.test()` 注册测试,用标准库里的断言函数验证结果,再通过 `deno test` 运行。它的特点不是“功能最多”,而是和运行时权限、TypeScript、覆盖率、并发执行放在同一套命令里管理。真正写项目时,重点不只是会写一个 `assertEquals`,还要知道异步资源怎么清理、权限怎么最小化、哪些测试适合并行,哪些测试必须隔离。
如果把 Deno 测试只理解成“能跑断言”,很容易写出本地偶尔通过、CI 经常失败的测试。Deno 的测试设计更强调运行时约束:测试代码默认没有额外权限,未关闭的异步操作会被检查,覆盖率也不需要额外插件。这些默认值会让刚上手的人觉得严格,但对长期维护很友好,因为很多隐蔽问题会在测试阶段就暴露出来。
## 追问
### 最小的 Deno 测试应该怎么写?
最小测试通常放在 `*_test.ts` 或 `.test.ts` 文件里,Deno 会自动发现这些文件。断言建议优先从 `jsr:@std/assert` 引入,旧项目里也可能看到 `https://deno.land/std/.../asserts.ts`,两者不要在同一个项目里混着升级。测试名要描述行为,而不是写成 `test add` 或 `works`,否则失败时排查成本很高。边界上,`assertEquals` 会做深比较,适合对象和数组;如果要比较同一个引用,才用 `assertStrictEquals`。实际项目里还要注意导入路径的稳定性,特别是从远程 URL 迁移到 JSR 或 npm 兼容包时,锁文件和版本号要一起维护。否则同一段测试在不同机器上可能解析到不同版本的断言库,报错信息也会不一致。
```ts
import { assertEquals } from "jsr:@std/assert";
function add(a: number, b: number) {
return a + b;
}
Deno.test("add returns sum of two numbers", () => {
assertEquals(add(1, 2), 3);
assertEquals(add(-1, 1), 0);
});
```
```bash
deno test
deno test src/math_test.ts
deno test --filter="add returns"
```
### 异步测试、异常测试和资源清理有什么坑?
异步测试的函数本身要 `async`,并且必须 `await` 被测 Promise,否则测试可能在异步错误抛出前就结束。同步异常用 `assertThrows`,Promise 拒绝用 `assertRejects`,这两个不要混用。Deno 默认会检查未关闭的 op、资源和异常退出,这比很多测试框架严格,也更容易暴露真实问题。踩坑最多的是启动 HTTP server、文件句柄或数据库连接后忘了在 `finally` 里关闭,短期看只是测试失败,长期看会让 CI 随机挂。如果确实有长连接或后台计时器无法在测试结束前自然关闭,可以临时关闭 `sanitizeOps` 或 `sanitizeResources`,但这应该是最后手段。更好的做法是把启动和关闭封装成工具函数,让每个测试都能明确释放资源。
```ts
import { assertRejects } from "jsr:@std/assert";
async function loadUser(id: number) {
if (id <= 0) throw new Error("invalid id");
return { id };
}
Deno.test("loadUser rejects invalid id", async () => {
await assertRejects(() => loadUser(0), Error, "invalid id");
});
```
### 测试里需要读文件、访问网络时权限怎么给?
Deno 的测试和运行代码一样受权限模型约束,读文件要 `--allow-read`,访问网络要 `--allow-net`。可以在命令行给权限,也可以在单个 `Deno.test` 的配置里声明权限;后者更适合单元测试,因为它能把权限边界写在测试旁边。取舍是命令行授权简单,适合本地临时跑;单测级授权更啰嗦,但 CI 和代码审查时更安全。不要为了省事长期使用 `--allow-all`,它会掩盖代码偷偷访问文件、环境变量或网络的行为。权限还会影响 Mock 策略:能用内存假对象替代真实文件和网络时,就不要为了测试方便放开系统权限。这样做的代价是多写一点测试替身,但收益是测试更快、更稳定,也更接近单元测试的边界。
```ts
Deno.test({
name: "reads fixture file",
permissions: { read: ["./fixtures"] },
async fn() {
const text = await Deno.readTextFile("./fixtures/user.json");
if (!text.includes("name")) throw new Error("bad fixture");
},
});
```
```bash
deno test --allow-read=./fixtures
deno test --allow-net=localhost:8000
```
### Deno 测试怎么组织才适合真实项目?
单元测试可以贴近源码放,例如 `src/user_test.ts`;集成测试可以放到 `tests/`,并在 `deno.json` 里统一配置 include 和 exclude。测试写法上推荐 Arrange、Act、Assert 三段式,但不用机械地写注释,关键是让准备数据、执行动作、验证结果一眼能分开。涉及数据库、临时目录、端口监听时,每个测试都要创建自己的隔离环境,不能依赖前一个测试留下的状态。并行执行能节省时间,但共享全局变量、固定端口、固定文件名的测试不适合直接并行。当测试之间共享 fixture 时,fixture 可以只读共享,但运行中产生的数据最好写入临时目录。固定写 `./tmp/result.json` 这类路径,在并行测试和重复运行时都容易互相污染。
```json
{
"tasks": {
"test": "deno test --allow-read=./fixtures",
"test:ci": "deno test --coverage=coverage --fail-fast"
},
"test": {
"include": ["src/**/*_test.ts", "tests/**/*.ts"],
"exclude": ["vendor/"]
}
}
```
### 覆盖率和 CI 里应该关注什么?
覆盖率可以用 `deno test --coverage=coverage` 生成原始数据,再用 `deno coverage` 输出文本、LCOV 或 HTML。覆盖率适合发现“完全没测到”的分支,但不应该变成唯一目标,因为 90% 覆盖率也可能没有断言关键行为。CI 里更重要的是固定命令、固定权限、失败快速暴露,并把网络类测试和纯单元测试分开。一个常见取舍是本地默认跑快速单测,合并前或夜间任务再跑慢集成测试,这样不会让开发反馈变得太慢。覆盖率目录也要在 CI 中清理,避免上一次运行留下的数据影响这一次报告。对于分支很多的业务代码,可以把覆盖率报告当成提示,再回头检查断言是不是验证了业务结果,而不是只执行了代码。
```bash
deno task test:ci
deno coverage coverage --lcov --output=coverage.lcov
deno coverage coverage --html
```
Deno 测试框架的优势在于默认严格、命令少、和运行时边界一致。写好它的关键不是堆断言,而是把权限、资源清理、异步失败和测试隔离一起设计好。服务端5月28日 06:55
Deno 的性能优化有哪些关键技巧?Deno 基于 Rust 和 V8 引擎构建,在运行时层面已经做了大量性能优化,但实际项目中如果不理解其底层机制,很容易写出低效的代码。Deno 的性能优化涉及启动加速、运行时调优、内存管理、并发模型和 I/O 处理等多个维度,掌握这些技巧是面试中的常见考察点。
## Deno 的性能瓶颈通常出现在哪些环节?
Deno 应用的性能瓶颈主要集中在三个环节:启动阶段的全量依赖加载、运行时的同步阻塞操作、以及内存中的大对象持有。面试中回答这个问题时,需要结合 Deno 的架构特点来分析——Deno 的 Rust 核心(crate deno_core)通过 opcall 机制与 V8 通信,每次 opcall 的开销在 2.x 版本中已优化到纳秒级,但频繁的跨边界调用仍然是性能敏感场景需要关注的重点。
## 如何优化 Deno 的启动性能?
Deno 的冷启动耗时主要花在模块解析和 TypeScript 编译上。优化启动性能的核心思路是减少启动时需要解析和编译的代码量。
**动态导入替代顶层全量加载**:将非必要依赖改为按需加载,避免启动时解析整个依赖树。
```typescript
// 顶层全量加载:启动时解析所有依赖
import { heavyProcessor } from "./heavy-processor.ts";
// 动态导入:只在需要时加载
async function processData(data: unknown) {
const { heavyProcessor } = await import("./heavy-processor.ts");
return heavyProcessor(data);
}
```
**利用 Deno 的编译缓存**:Deno 会将编译结果缓存在 `DENO_DIR` 目录中(默认 `~/.cache/deno`),二次启动直接读取缓存。在 CI/CD 环境中,可以通过挂载持久化的缓存目录来避免每次构建都重新编译。
**使用 `deno compile` 生成单文件可执行文件**:对于部署场景,`deno compile` 将运行时和应用代码打包成单文件,省去运行时的模块解析和编译开销,启动速度可提升 30% 以上。
**V8 Snapshot 机制**:Deno 在构建时就通过 V8 Snapshot 预先序列化了内置 API 的初始化状态,这意味着 `Deno.readFile`、`fetch` 等全局 API 在启动时无需重新初始化。自定义应用也可以利用 `deno_core` 的 snapshot 功能来预初始化重度依赖。
## Deno 运行时有哪些关键的性能优化手段?
运行时优化的核心是减少不必要的计算开销和跨 Rust/V8 边界的调用次数。
**减少 opcall 频率**:Deno 通过 opcall(类似系统调用的机制)在 V8 和 Rust 之间通信。虽然 2.x 版本已经将单次 opcall 开销从早期的微秒级降到纳秒级(约 40ns),但在高频调用场景下仍然需要注意。例如,逐行读取文件比一次性读取产生更多的 opcall:
```typescript
// 低效:多次 opcall
const file = await Deno.open("large.txt");
const buf = new Uint8Array(1024);
while (await file.read(buf)) {
processChunk(buf);
}
// 高效:一次 opcall 读取全部内容
const content = await Deno.readTextFile("large.txt");
processContent(content);
```
**使用 Web 标准 API 而非 Deno 特有 API**:Deno 的 Web 标准 API(如 `fetch`、`ReadableStream`、`TextEncoder`)经过高度优化,优先使用这些 API 可以获得更好的性能和可移植性。
**TypeScript 运行时类型检查优化**:Deno 默认在开发模式下进行类型检查,但类型检查是 CPU 密集型操作。在生产环境中使用 `--no-check` 标志跳过类型检查,可显著减少启动和热重载时间:
```bash
deno run --no-check app.ts
```
**选择高效的数据结构**:在频繁查找场景下,`Map` 和 `Set` 的性能优于普通对象,因为 V8 对 Map/Set 有专门的优化路径。
## 如何处理 Deno 中的内存优化?
Deno 的内存管理依赖 V8 的垃圾回收器,但应用层面的不当使用仍会导致内存泄漏和 GC 压力过大。
**及时释放资源**:Deno 的资源表(Resource Table)维护着所有打开的文件、网络连接等资源。未关闭的资源会持续占用文件描述符和内存,且资源表本身也会增长。始终在 `finally` 块中关闭资源:
```typescript
const file = await Deno.open("data.txt");
try {
const content = await Deno.readAll(file);
return processContent(content);
} finally {
file.close(); // 确保资源从资源表中移除
}
```
**流式处理替代全量加载**:处理大文件时,使用 `ReadableStream` 进行流式处理,避免将整个文件加载到内存:
```typescript
async function processLargeFile(path: string) {
const file = await Deno.open(path);
const stream = file.readable
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TransformStream({
transform(chunk, controller) {
controller.enqueue(processChunk(chunk));
}
}));
for await (const result of stream) {
handleResult(result);
}
}
```
**WeakRef 和 FinalizationRegistry**:对于缓存场景,使用 `WeakRef` 避免缓存对象阻止 GC 回收,配合 `FinalizationRegistry` 在对象被回收时清理关联资源。
**控制内存上限**:通过 `--v8-flags=--max-old-space-size=4096` 限制 V8 堆内存上限,防止内存泄漏导致进程 OOM。同时可以通过 `Deno.memoryUsage()` 监控内存使用情况。
## Deno 的并发模型如何影响性能?
Deno 的并发模型基于 V8 的事件循环,单线程内通过异步 I/O 实现非阻塞。对于 CPU 密集型任务,Deno 提供了 Web Worker 和 `Deno.UnsafelyUseFinalizationRegistry` 等机制。
**Web Worker 并行处理 CPU 密集型任务**:
```typescript
// main.ts
const worker = new Worker(
new URL("./worker.ts", import.meta.url),
{ type: "module" }
);
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
console.log("Result:", e.data);
};
```
```typescript
// worker.ts
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};
```
**并发控制**:高并发场景下,无限制地发起异步操作会导致事件循环压力过大、文件描述符耗尽。需要实现并发控制器:
```typescript
async function parallelLimit<T>(
tasks: (() => Promise<T>)[],
limit: number
): Promise<T[]> {
const results: T[] = [];
const executing = new Set<Promise<void>>();
for (const [index, task] of tasks.entries()) {
const p = task().then((result) => {
results[index] = result;
executing.delete(p);
});
executing.add(p);
results[index] = undefined as T;
if (executing.size >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
```
**Deno.serve 的并发性能**:Deno 2.x 中 `Deno.serve`(或标准库的 `serve`)是构建 HTTP 服务的主要 API,它基于 Rust 的 hyper 库实现,性能接近原生 HTTP 服务器。基准测试中,Deno 2.x 的 HTTP 吞吐量约 78k req/s(简单 JSON 响应),显著优于 Node.js 22 的约 65k req/s。
## Deno 的 I/O 性能如何优化?
I/O 是大多数 Web 应用的核心瓶颈。Deno 的 I/O 操作通过 Rust 异步运行时(tokio)实现,天然支持非阻塞 I/O。
**始终使用异步 I/O**:Deno 中同步 I/O API(如 `Deno.readTextFileSync`)会阻塞事件循环,仅适用于初始化阶段或 CLI 工具。生产环境必须使用异步版本。
**利用 HTTP/2**:Deno 的 `Deno.serve` 支持 HTTP/2(通过 ALPN 协商),多路复用特性可以在单个连接上并行传输多个请求,减少连接建立开销:
```typescript
Deno.serve({
port: 8000,
handler: async (req) => {
return new Response(JSON.stringify({ status: "ok" }), {
headers: { "Content-Type": "application/json" },
});
},
alpnProtocols: ["h2", "http/1.1"],
});
```
**响应压缩**:对响应体进行 gzip 或 brotli 压缩,显著减少传输体积。Deno 标准库提供了压缩工具:
```typescript
import { gzip } from "https://deno.land/std@0.224.0/encoding/gzip.ts";
Deno.serve({
port: 8000,
handler: async (req) => {
const data = JSON.stringify(await getLargeDataset());
const acceptEncoding = req.headers.get("accept-encoding") ?? "";
if (acceptEncoding.includes("gzip")) {
const compressed = await gzip(new TextEncoder().encode(data));
return new Response(compressed, {
headers: {
"Content-Encoding": "gzip",
"Content-Type": "application/json",
},
});
}
return new Response(data, {
headers: { "Content-Type": "application/json" },
});
},
});
```
**KV 存储优化**:Deno KV 是内置的键值数据库,基于 SQLite 实现。在高写入场景下,使用原子事务批量写入,减少事务开销:
```typescript
const kv = await Deno.openKv();
// 批量原子写入
const atomic = kv.atomic();
for (const entry of entries) {
atomic.set(entry.key, entry.value);
}
await atomic.commit();
```
## 如何监控和诊断 Deno 的性能问题?
性能优化的前提是准确测量。Deno 提供了多种监控和诊断手段。
**Deno.memoryUsage()**:实时获取 RSS、堆内存使用量等指标,用于内存监控和泄漏检测:
```typescript
const mem = Deno.memoryUsage();
console.log(`RSS: ${(mem.rss / 1024 / 1024).toFixed(2)} MB`);
console.log(`Heap: ${(mem.heapUsed / 1024 / 1024).toFixed(2)} / ${(mem.heapTotal / 1024 / 1024).toFixed(2)} MB`);
```
**性能测量 API**:使用 Web 标准的 `performance.now()` 和 `PerformanceObserver` 进行精确计时:
```typescript
const start = performance.now();
await someOperation();
const duration = performance.now() - start;
console.log(`Operation took ${duration.toFixed(2)}ms`);
```
**V8 内置分析器**:通过 `--v8-flags` 启用 V8 的 CPU 分析器和堆分析器:
```bash
# CPU 分析
deno run --v8-flags=--prof app.ts
# 堆快照
deno run --v8-flags=--heap-prof app.ts
```
**Deno 命令行工具**:`deno bench` 用于基准测试,`deno task` 用于任务编排。基准测试示例:
```typescript
// bench.ts
Deno.bench("JSON parse 10k items", () => {
JSON.parse(largeJsonString);
});
Deno.bench("Map vs Object lookup", () => {
userMap.get("key-9999");
});
```
**权限对性能的影响**:Deno 的安全模型要求运行时检查权限,在高频 I/O 场景下会产生可测量的开销。如果确认环境安全,可以在生产环境中通过 `--allow-all` 减少权限检查开销,但需权衡安全性和性能。
掌握以上优化技巧,需要理解一个核心原则:Deno 的性能优势来自 Rust 层的高效实现和 V8 的 JIT 优化,应用层的优化应围绕减少不必要的跨层调用、避免阻塞事件循环、合理管理内存和资源展开。面试中回答 Deno 性能优化问题时,结合底层原理和实际场景给出具体方案,比罗列通用优化技巧更有说服力。
服务端5月27日 20:26
什么是 Deno?它和 Node.js 有什么区别?## Deno 的核心设计理念
Deno 由 Node.js 创始人 Ryan Dahl 在 2018 年发起,动机是他公开承认 Node.js 有几个设计决策无法在不破坏兼容性的前提下修复:默认不安全的执行环境、混乱的 node_modules 机制、以及 CommonJS 与 ES Modules 并存的模块系统。Deno 从零开始,用 Rust 重写了底层,试图给出更干净的答案。
## Deno 和 Node.js 的关键区别
**安全模型**:Deno 默认沙箱执行,脚本无法读写文件、访问网络或读取环境变量,必须通过 `--allow-read`、`--allow-net` 等标志显式授权。Node.js 默认完全信任脚本,没有权限墙。
**模块与包管理**:Deno 用 URL 直接导入模块,不依赖 package.json 和 node_modules。Deno 2.0 起 `npm:` 前缀已稳定支持,可以 `import express from "npm:express"` 直接使用 npm 包。Node.js 仍以 npm + package.json 为核心。
**TypeScript**:Deno 原生执行 .ts 文件,零配置。Node.js 22 虽已实验性支持,但生产环境仍需配置转译。
**API 风格**:Deno 全部采用 Promise/async-await,Node.js 保留了大量回调风格的旧 API。
**底层实现**:Deno 基于 Rust + V8,Node.js 基于 C++ + V8 + libuv。
```bash
# Deno 权限示例
deno run --allow-net --allow-read server.ts
```
## 2026 年选型建议
- **选 Node.js**:已有大型项目、依赖 npm 生态深度、团队招聘池大
- **选 Deno**:新项目优先安全(如跑不可信代码)、边缘部署(Deno Deploy)、TypeScript-first
- **考虑 Bun**:CI/CD 追求极致速度、高吞吐 HTTP 场景
## 追问方向
- Deno 的权限模型能防范供应链攻击吗?-- 能限制恶意包的文件和网络访问,但 `--allow-all` 等于没有限制
- Deno 2.0 的 npm 兼容性有没有坑?-- 大部分主流包可用,但依赖原生 C++ 模块的包可能失败
- 为什么 Ryan Dahl 认为node_modules是个错误?-- 它导致幽灵依赖、磁盘浪费、安装慢,Deno 用全局缓存 + URL 导入替代服务端5月27日 20:20
Deno 生态有哪些主流库和工具?Deno 2.x 的生态已从早期的 deno.land/x 过渡到 JSR(JavaScript Registry)作为主力包注册表,同时通过 `npm:` 前缀直接引用约 98% 的 npm 包。Web 框架首选 Hono(轻量、跨 Deno/Bun/Cloudflare Workers 运行时)和 Oak(Koa 风格中间件),Fresh 是官方全栈框架但社区活跃度一般。数据库层 Drizzle ORM 是目前最成熟的 TypeScript ORM,支持 PostgreSQL/MySQL/SQLite。Deno KV 是内置键值存储,零依赖适合轻量场景。认证用 djwt 处理 JWT,@deno/kv-oauth 做 OAuth2。工具链全部内置——fmt、lint、test、compile、doc 一条命令搞定,Deno 2 还新增了 pack、bump-version、ci 等子命令。部署用 Deno Deploy,免费额度每天 10 万请求、35+ 边缘节点零冷启动。
## 追问
### JSR 和 npm 有什么区别?
JSR 是 Deno 主导的 TypeScript 优先注册表,源码直接发 TS 无需编译,自动为 npm 生态生成兼容包。npm 以 CommonJS/编译后 JS 为主。Deno 两个都能用:JSR 用 `jsr:` 前缀,npm 用 `npm:` 前缀。新项目优先发 JSR,兼容两边的开发者。
### Hono 和 Oak 怎么选?
Hono 更轻量、跨运行时,中间件生态丰富,适合 API 和边缘计算场景。Oak 是 Deno 专属框架,API 接近 Koa 学习成本低,但生态不如 Hono。新项目建议 Hono,已有 Koa 经验的团队可以快速上手 Oak。
### Deno KV 能替代 Redis 吗?
轻量场景可以:键值读写、原子操作、版本控制都支持,零依赖开箱即用。但不支持 TTL 自动过期、没有 pub/sub、查询只有前缀扫描。需要过期策略或发布订阅时还是得接 Redis。
### 从 Node.js 迁移到 Deno 难吗?
Deno 2 已支持 ~98% 的 npm 包,大部分依赖加 `npm:` 前缀就能跑。主要差异:文件系统用 `Deno.readTextFile` 而非 `fs.readFile`;运行时需要 `--allow-net` 等权限标志。依赖 sharp、bcrypt 等 Node 原生模块的项目可能有兼容问题,建议先用 `deno info` 检查依赖树。
### Deno Deploy 和传统服务器部署哪个好?
Deno Deploy 适合无状态 API 和边缘计算,全球 35+ 节点低延迟,免费额度 generous。限制:WebSocket 连接有超时、无持久文件系统、不支持长驻进程。传统 VPS/容器部署更灵活,适合需要完整运行时的项目。两者不冲突,API 层 Deploy + 重计算层 VPS 是常见搭配。服务端5月27日 20:17
Deno 的部署和运维有哪些最佳实践?## 核心实践概览
Deno 生产部署的关键在于:容器化打包、权限最小化、健康检查三板斧,以及选择合适的部署平台。面试中考察的重点不是你会写多少 Dockerfile,而是你能否说清每个配置背后的取舍。
## 容器化部署怎么选?
Deno 官方提供 `denoland/deno` 镜像,生产环境推荐多阶段构建:先用 `deno compile` 将 TypeScript 编译为单文件二进制,再用精简基础镜像(如 `debian:slim`)运行。这样做的好处是最终镜像小、启动快、不暴露源码。
注意:Deno 2.x 已发布,不要再写 `denoland/deno:1.38.0` 这种过时版本。编译命令也有所变化,建议查阅最新文档。
容器内务必以非 root 用户运行,Dockerfile 末尾加 `USER deno` 是基本操作。
## 权限控制为什么重要?
Deno 和 Node.js 最大的架构差异就是默认安全。生产环境中坚决不用 `-A`(全量授权),而是按需授予:
- 网络访问用 `--allow-net=api.example.com` 限定域名
- 文件读取用 `--allow-read=/app/data` 限定路径
- 环境变量用 `--allow-env=PORT,DB_URL` 限定变量名
面试中如果只答出 `--allow-net` 这种粗粒度权限,说明你没在生产环境用过 Deno。
## 部署平台如何选择?
三种主流方案:
- **Deno Deploy**:官方边缘计算平台,零配置部署,适合轻量 API 和 SSR 应用。2026 年 2 月已 GA,Classic 版将在 7 月下线,需要迁移到新平台。
- **容器化自建**:Docker + K8s,适合需要精细控制或有复杂依赖的场景。配置资源限制(requests/limits)、存活探针和就绪探针是基本要求。
- **PaaS 托管**:Railway、Vercel 等平台,适合快速上线,但定制空间有限。
选型依据:流量规模、延迟要求、团队运维能力。不要为了用 Deno Deploy 而用,数据库连接密集型场景自建集群更稳。
## 健康检查和监控怎么做?
健康检查端点是生产标配:
- `/health` 返回进程存活状态,用于 K8s livenessProbe
- `/ready` 返回服务就绪状态(依赖是否连上),用于 readinessProbe
监控方面,结构化日志(JSON 格式)比 console.log 更有利于日志平台解析。Deno 2.x 支持 `Deno.memoryUsage()` 获取内存指标,配合定时上报可以排查内存泄漏。
## CI/CD 有什么坑?
Deno 项目的 CI 流程比 Node.js 简单——不需要 `npm install`,`deno cache` 一步搞定依赖。但要注意:
- `deno lint` 和 `deno fmt --check` 必须加,Deno 内置了这些工具没有理由不用
- Docker 构建时先 `COPY deno.json` 再 `deno cache`,利用层缓存加速构建
- 镜像 tag 用 git SHA 而不是 `latest`,否则回滚时找不到版本
## 追问方向
- Deno Deploy 和 Cloudflare Workers 在架构上有什么区别?(冷启动、运行时限制、KV 存储)
- `deno compile` 编译出的二进制在 Alpine 镜像里能直接跑吗?(不能,Alpine 用 musl,需要静态编译或用 glibc 镜像)
- Deno 的权限系统在容器内被绕过怎么办?(容器本身是安全边界,Deno 权限是应用层防护,两者互补)
服务端5月27日 20:14
如何使用 deno compile 编译可执行文件?## 什么是 deno compile?
deno compile 是 Deno 内置的编译工具,能把 TypeScript/JavaScript 代码连同运行时一起打包成独立可执行文件。目标机器不需要安装 Deno,直接运行即可。它的底层原理是把代码和依赖绑定为 eszip 格式,再注入到精简版 Deno 运行时(denort)二进制中——所以它并不是真正编译成机器码,而是"打包+嵌入"。
## 基本编译命令
```bash
deno compile --allow-net --allow-read app.ts
```
编译时必须指定权限标志,运行时无法再修改。输出文件默认与源文件同名,可用 `--output` 自定义:
```bash
deno compile --allow-net --output=myapp app.ts
```
## 交叉编译与目标平台
一条命令即可编译到其他平台:
```bash
deno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux app.ts
deno compile --target=aarch64-apple-darwin --output=myapp-mac app.ts
deno compile --target=x86_64-pc-windows-msvc --output=myapp.exe app.ts
```
这在 CI/CD 中做批量发布时很实用,不用准备多台构建机器。
## 三个注意事项
**文件体积大。** 编译产物通常 50-100 MB,因为包含了完整 V8 引擎和 Deno 运行时。对体积敏感的场景可用 upx 压缩,但稳定性需自行验证。
**动态导入不会自动打包。** 静态分析能识别的动态 import 会被包含,但运行时拼装的路径不会。需要用 `--include` 显式声明:
```bash
deno compile --include=./plugins/plugin.ts app.ts
```
Web Worker 的代码同理,也需 `--include` 手动加入。
**权限编译时锁定。** `--allow-net` 等标志在编译时写死,运行时无法突破,也无法降级。这意味着如果将来需要新权限,必须重新编译。
## deno compile 与 Node.js 打包工具对比
| 维度 | deno compile | pkg (Node) | nexe (Node) |
|------|-------------|------------|-------------|
| 配置复杂度 | 零配置,一条命令 | 需要 package.json 和配置 | 需要配置和构建脚本 |
| 跨平台编译 | 原生支持 --target | 有限支持 | 有限支持 |
| 产物体积 | 50-100 MB | 40-80 MB | 40-70 MB |
| 维护状态 | Deno 官方维护 | 社区维护,更新慢 | 社区维护,更新慢 |
deno compile 的核心优势不在体积,而在于零配置和官方长期维护。
## 面试追问方向
- deno compile 的产物为什么体积大?能否压缩?——因为嵌入了 V8 和 denort,upx 可压缩但有兼容风险。
- 动态导入的模块为什么不会被打包?如何解决?——编译器只做静态分析,运行时才能确定路径的 import 会被跳过,用 `--include` 显式包含。
- 编译时锁定的权限是否意味着安全性更强?——是,但也丧失了灵活性,需根据场景取舍。服务端5月27日 20:12
Deno 如何处理模块导入和依赖管理?## 核心答案
Deno 采用 URL 直接导入模块,没有 package.json 和 node_modules,依赖管理通过 Import Maps、deps.ts 文件或 deno.json 的 imports 字段集中管理,模块全局缓存于本地。
## 导入方式
Deno 支持 URL 导入、相对路径导入和 Import Maps 别名导入三种方式:
```typescript
// URL 直接导入(指定版本)
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// 相对路径导入(必须带扩展名)
import { utils } from "./utils.ts";
// 通过 Import Maps 使用别名
import { Application } from "oak";
```
Import Maps 在 `deno.json` 中配置,可将 URL 映射为简短别名,是目前推荐的做法。
## 依赖管理方案
早期 Deno 推荐 `deps.ts` 模式——将所有远程依赖集中到一个文件重新导出,应用代码只从 deps.ts 导入。现在更推荐使用 `deno.json` 的 imports 字段,本质上是标准 Import Maps:
```json
{
"imports": {
"oak": "jsr:@oak/oak@^12.6.1",
"std/": "https://deno.land/std@0.208.0/"
}
}
```
JSR(jsr.io)是 Deno 推出的现代包注册表,支持 TypeScript 原生发布,配合 `deno add jsr:@oak/oak` 直接安装依赖。
## 与 Node.js 的关键区别
- **无 node_modules**:模块下载后全局缓存,不污染项目目录
- **无 package.json**:用 deno.json 或 deps.ts 替代
- **必须带扩展名**:本地导入必须写 `.ts`,与浏览器行为一致
- **权限控制**:导入远程模块需要 `--allow-net`,运行时受沙箱约束
## 版本锁定
`deno.lock` 文件记录依赖的精确版本和完整性哈希,类似 npm 的 package-lock.json。CI 环境中用 `deno install --frozen` 确保依赖不可变。
## 追问:URL 导入有什么安全隐患?如何规避?
URL 导入指向的代码可能被篡改(供应链攻击)。规避方式:始终锁定版本号、使用 lock 文件校验哈希、优先从 JSR 等可信注册表安装、配置 `DENO_AUTH_TOKENS` 访问私有仓库。服务端5月27日 20:12
Deno 标准库有哪些常用模块?## 核心回答
Deno 标准库(@std)已稳定至 v1,现通过 JSR 分发,包含 37 个独立包。最常用的有:**@std/fs**(文件系统)、**@std/path**(路径处理)、**@std/http**(HTTP 服务)、**@std/assert**(断言测试)、**@std/async**(异步工具)、**@std/encoding**(编解码)、**@std/collections**(集合操作)、**@std/fmt**(格式化输出)。
导入方式已从 `deno.land/std` 迁移至 JSR:
```typescript
// 旧方式(已不推荐)
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// 新方式(JSR)
import { serve } from "@std/http";
```
## 模块详解
**@std/fs** 提供 ensureDir、copy、walk 等文件操作,比手动调用 Deno API 更安全——自动处理目录不存在等边界情况。
**@std/path** 等价于 Node.js 的 path 模块,提供 join、resolve、basename 等,跨平台兼容。
**@std/http** 用于快速搭建 HTTP 服务,`serve()` 接收 Request/Response 标准对象,无需第三方框架。
**@std/assert** 是 Deno 测试的标配断言库,assertEquals、assertThrows 覆盖绝大多数场景。
**@std/async** 提供 delay、retry、debounce 等异步工具,retry 支持指数退避策略。
**@std/encoding** 处理 base64、hex、varint 等编解码,是网络和存储场景的基础依赖。
## 注意事项
部分模块仍标记为 UNSTABLE(0.x 版本),如 @std/log、@std/datetime、@std/io,生产环境慎用。标准库各包独立版本管理,成熟度不同,导入前需确认稳定状态。
## 追问方向
- Deno 标准库和 Node.js 内置模块相比,设计理念有什么不同?(提示:ESM-only、无全局污染、独立版本)
- @std/http 能否替代 Oak 等框架?什么场景下需要引入第三方框架?(提示:路由、中间件、请求校验)
- 标准库模块的 UNSTABLE 标记意味着什么?如何判断能否用于生产?(提示:语义化版本、JSR 标注)服务端5月27日 20:11
Deno 的权限系统是如何工作的?Deno 采用"默认拒绝"的安全模型——脚本启动时没有任何权限,必须通过命令行标志显式授权才能访问文件、网络、环境变量等资源。这套权限系统是 Deno 区别于 Node.js 的核心安全特性。
## 核心机制
权限以 `--allow-*` 标志授予,支持通配和精确指定两种模式:
```bash
# 通配:允许所有网络访问
deno run --allow-net app.ts
# 精确:只允许访问指定域名
deno run --allow-net=api.example.com app.ts
```
主要权限标志包括 `--allow-read`、`--allow-write`、`--allow-net`、`--allow-env`、`--allow-run`、`--allow-sys`、`--allow-hrtime` 和 `--allow-ffi`,均可通过 `=` 指定白名单。Deno 1.36 起还支持 `--deny-*` 黑名单,优先级高于 `--allow-*`,可在宽泛授权下排除特定资源。
## 运行时权限查询
代码中可通过 `Deno.permissions` API 检查和请求权限:
```typescript
const status = await Deno.permissions.query({ name: "net" });
// status.state → "granted" | "prompt" | "denied"
const req = await Deno.permissions.request({ name: "read", path: "/tmp" });
// 运行时弹出交互提示
```
也可调用 `Deno.permissions.revoke()` 主动放弃已获权限,实现最小权限的动态收缩。
## 子进程权限的陷阱
`--allow-run` 授予的子进程不在 Deno 沙箱内运行,它继承宿主系统的完整权限,不受 Deno 权限标志约束。因此只应允许运行明确可信的命令,如 `--allow-run=git,curl`。
## 与 Node.js 的关键区别
Node.js 默认拥有全部系统权限,依赖 `fs` 模块即可读写任意文件,安全边界完全依赖操作系统层面。Deno 则从运行时层面强制权限隔离,每个资源访问请求都需经过权限检查,恶意依赖无法静默越权。
## 追问
- **权限白名单和黑名单同时存在时,哪个优先?** `--deny-*` 优先。即使 `--allow-read=/app` 已授权,`--deny-read=/app/secret` 仍会阻止对该目录的访问。
- **如何在 CI 中自动处理权限提示?** 使用 `--no-prompt` 标志,未授权的访问直接抛出 PermissionDenied 错误而非交互提示,适合自动化流水线。