Node.js · #runtime#deno#nodejs

Deno vs Node.js:运行时之争

2025.07.09 7 min 3.0k
// 目录 · contents

前言

Deno由Node.js的原作者Ryan Dahl于2018年发布,旨在解决Node.js的设计缺陷。经过多年发展,Deno 2.0在保持其安全优先理念的同时,大幅提升了Node.js兼容性。本文将从多个维度深入对比这两个运行时。

架构对比

graph TB
    subgraph "Node.js Architecture"
        NJS[JavaScript/TypeScript] --> NV8[V8 Engine]
        NV8 --> NAPI[Node API / N-API]
        NAPI --> NLIBUV[libuv<br>Event Loop]
        NAPI --> NOPENSSL[OpenSSL]
        NAPI --> NHTTP[llhttp]
        NJS --> NNPM[npm/yarn/pnpm<br>node_modules]
    end

    subgraph "Deno Architecture"
        DJS[JavaScript/TypeScript] --> DV8[V8 Engine]
        DV8 --> DRUST[Rust Core<br>deno_core]
        DRUST --> DTOKIO[Tokio<br>Async Runtime]
        DRUST --> DRUSTLS[Rustls<br>TLS]
        DRUST --> DHTTP[hyper<br>HTTP]
        DJS --> DMOD[URL Imports<br>+ npm: specifier]
    end

关键区别:Deno使用Rust和Tokio构建,而Node.js使用C++和libuv。

安全模型

Deno最显著的特性是默认安全——默认情况下,程序没有任何文件系统、网络、环境变量的访问权限。

graph LR
    subgraph "Node.js"
        NS[脚本] --> NFS[文件系统 - 完全访问]
        NS --> NNET[网络 - 完全访问]
        NS --> NENV[环境变量 - 完全访问]
        NS --> NSUB[子进程 - 完全访问]
    end

    subgraph "Deno"
        DS[脚本] --> DFS[文件系统 - 需要--allow-read/write]
        DS --> DNET[网络 - 需要--allow-net]
        DS --> DENV[环境变量 - 需要--allow-env]
        DS --> DSUB[子进程 - 需要--allow-run]
    end

    style NFS fill:#d32f2f,color:#fff
    style NNET fill:#d32f2f,color:#fff
    style DFS fill:#388e3c,color:#fff
    style DNET fill:#388e3c,color:#fff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Deno权限控制

# 无权限运行(最安全)
deno run script.ts

# 只允许读取特定目录
deno run --allow-read=/data script.ts

# 只允许访问特定域名
deno run --allow-net=api.example.com,cdn.example.com script.ts

# 允许读写特定目录 + 网络
deno run --allow-read=/data --allow-write=/output --allow-net script.ts

# 允许环境变量(指定变量名)
deno run --allow-env=API_KEY,DATABASE_URL script.ts

# 允许运行特定子进程
deno run --allow-run=git,node script.ts

# 所有权限(等同于Node.js,不推荐)
deno run --allow-all script.ts
# 或简写
deno run -A script.ts
1
2
3
4
5
6
7
8
9
// Deno权限API
const status = await Deno.permissions.query({ name: "read", path: "/etc" });
console.log(status.state); // "granted" | "denied" | "prompt"

// 运行时请求权限
const result = await Deno.permissions.request({ name: "net", host: "api.example.com" });
if (result.state === "granted") {
const resp = await fetch("https://api.example.com/data");
}

TypeScript支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Node.js: 需要额外配置
// 1. 安装typescript + ts-node/tsx
// 2. 配置tsconfig.json
// 3. 使用tsx运行或编译后运行

// 项目设置
// npm install -D typescript @types/node ts-node
// npx tsc --init

// 运行方式
// npx tsx src/app.ts
// 或 node --loader ts-node/esm src/app.ts

// ============================================

// Deno: 原生支持,零配置
// deno run app.ts

// Deno自带类型检查
// deno check app.ts

// 跳过类型检查(更快)
// deno run --no-check app.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Deno示例 - 无需任何配置
interface User {
id: number;
name: string;
email: string;
}

async function fetchUser(id: number): Promise<User> {
const resp = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
return resp.json();
}

const user = await fetchUser(1);
console.log(`Hello, ${user.name}!`);

// 直接运行:
// deno run --allow-net app.ts

模块系统

graph TB
    subgraph "Node.js Module System"
        NPM[npm Registry] --> NM[node_modules/]
        NM --> PKG[package.json<br>dependencies]
        NM --> |嵌套依赖| NESTED[node_modules/<br>  express/<br>    node_modules/<br>      ...]
    end

    subgraph "Deno Module System"
        URL[URL Import<br>https://deno.land/std] --> CACHE[全局缓存<br>~/.cache/deno/]
        NPMSPEC[npm: specifier<br>npm:express@4] --> CACHE
        JSR[JSR Registry<br>jsr:@std/path] --> CACHE
    end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Node.js模块导入
import express from 'express'; // npm包
import { readFile } from 'fs/promises'; // 内置模块
import { myUtil } from './utils.js'; // 本地文件

// Deno模块导入 - 多种方式
// 1. URL导入(Deno原生方式)
import { serve } from "https://deno.land/[email protected]/http/server.ts";

// 2. npm:协议(兼容npm生态)
import express from "npm:express@4";
import chalk from "npm:chalk@5";

// 3. JSR (JavaScript Registry - Deno推荐)
import { join } from "jsr:@std/path@1";
import { parse } from "jsr:@std/csv@1";

// 4. Import Maps(配置别名)
// deno.json:
// {
// "imports": {
// "@std/": "jsr:@std/",
// "express": "npm:express@4",
// "~/": "./src/"
// }
// }

// 使用别名导入
import { join } from "@std/path";
import express from "express";
import { config } from "~/config.ts";

deno.json配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// deno.json
{
// Import Maps
"imports": {
"@std/": "jsr:@std/",
"express": "npm:express@4",
"hono": "jsr:@hono/hono@4",
"~/": "./src/"
},

// TypeScript编译选项
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "react"
},

// 任务(类似npm scripts)
"tasks": {
"dev": "deno run --watch --allow-net --allow-read src/main.ts",
"start": "deno run --allow-net --allow-read src/main.ts",
"test": "deno test --allow-read",
"lint": "deno lint",
"fmt": "deno fmt",
"check": "deno check src/**/*.ts"
},

// 格式化配置
"fmt": {
"indentWidth": 2,
"semiColons": true,
"singleQuote": true
},

// Lint配置
"lint": {
"rules": {
"tags": ["recommended"]
}
}
}

标准库对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// ===== 文件操作 =====

// Node.js
import { readFile, writeFile } from 'fs/promises';
const data = await readFile('file.txt', 'utf-8');
await writeFile('output.txt', data);

// Deno
const data = await Deno.readTextFile('file.txt');
await Deno.writeTextFile('output.txt', data);

// ===== HTTP服务器 =====

// Node.js
import { createServer } from 'http';
const server = createServer((req, res) => {
res.writeHead(200);
res.end('Hello');
});
server.listen(3000);

// Deno (内置Web标准API)
Deno.serve({ port: 3000 }, (req) => {
return new Response('Hello');
});

// ===== HTTP请求 =====

// Node.js (需要node-fetch或内置fetch)
const resp = await fetch('https://api.example.com/data');
const json = await resp.json();

// Deno (fetch是全局内置的)
const resp = await fetch('https://api.example.com/data');
const json = await resp.json();

// ===== 测试 =====

// Node.js (内置test runner, Node 20+)
import { test, describe, it } from 'node:test';
import assert from 'node:assert';

describe('math', () => {
it('should add', () => {
assert.strictEqual(1 + 1, 2);
});
});

// Deno (内置测试)
Deno.test('math - should add', () => {
assertEquals(1 + 1, 2);
});

// Deno BDD风格
import { describe, it, expect } from "jsr:@std/testing/bdd";

describe('math', () => {
it('should add', () => {
expect(1 + 1).toBe(2);
});
});

Web框架对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Express (Node.js)
import express from 'express';
const app = express();

app.get('/api/users/:id', async (req, res) => {
const user = await getUser(req.params.id);
res.json(user);
});

app.listen(3000);

// ============================================

// Hono (Deno / Node.js / Bun / Workers)
import { Hono } from 'hono';
const app = new Hono();

app.get('/api/users/:id', async (c) => {
const user = await getUser(c.req.param('id'));
return c.json(user);
});

Deno.serve(app.fetch);

// ============================================

// Oak (Deno原生)
import { Application, Router } from "jsr:@oak/oak@16";

const router = new Router();
router.get('/api/users/:id', async (ctx) => {
const user = await getUser(ctx.params.id);
ctx.response.body = user;
});

const app = new Application();
app.use(router.routes());
await app.listen({ port: 3000 });

Deno Deploy

graph TB
    CODE[Source Code] --> DEPLOY[Deno Deploy]
    DEPLOY --> EDGE1[Edge Node<br>Asia]
    DEPLOY --> EDGE2[Edge Node<br>Europe]
    DEPLOY --> EDGE3[Edge Node<br>Americas]

    USER1[Users Asia] --> EDGE1
    USER2[Users Europe] --> EDGE2
    USER3[Users Americas] --> EDGE3

    EDGE1 --> KV[(Deno KV<br>Global Database)]
    EDGE2 --> KV
    EDGE3 --> KV
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Deno Deploy示例 - 部署到全球边缘
import { Hono } from "jsr:@hono/hono";

const app = new Hono();
const kv = await Deno.openKv(); // Deno KV - 全球分布式数据库

app.get("/api/visits", async (c) => {
const key = ["visits", "total"];
const result = await kv.get<number>(key);
const count = (result.value ?? 0) + 1;
await kv.set(key, count);
return c.json({ visits: count });
});

app.get("/", (c) => {
return c.html(`<h1>Hello from the Edge!</h1>`);
});

Deno.serve(app.fetch);
1
2
3
4
5
6
7
# 部署到Deno Deploy
# 方式1: GitHub集成(推荐)
# 在 dash.deno.com 连接GitHub仓库

# 方式2: CLI部署
deno install -Agf jsr:@deno/deployctl
deployctl deploy --project=my-project src/main.ts

Node.js兼容性(Deno 2.0+)

flowchart TB
    DENO2[Deno 2.0] --> COMPAT{兼容性}
    COMPAT --> NPM[npm包支持<br>大多数npm包可用]
    COMPAT --> NODEAPI[Node.js API<br>fs, path, http...]
    COMPAT --> PKGJSON[package.json支持]
    COMPAT --> NM[node_modules支持]

    NPM --> EXAMPLE1["import express from 'npm:express'"]
    NODEAPI --> EXAMPLE2["import { readFile } from 'node:fs'"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Deno 2.0运行Node.js代码
// 大多数Node.js代码可以直接运行

// 使用Node.js内置模块
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { createServer } from "node:http";

// 使用npm包
import chalk from "npm:chalk@5";
import lodash from "npm:lodash@4";

console.log(chalk.green("Hello from Deno with npm packages!"));

// 甚至可以使用package.json
// deno会自动读取package.json中的dependencies

迁移指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 从Node.js迁移到Deno的常见变化

// 1. 文件扩展名必须写全
// Node.js
import { helper } from './utils'; // 可以省略.js
// Deno
import { helper } from './utils.ts'; // 必须写.ts

// 2. 使用node:前缀访问Node API
// Node.js
import fs from 'fs';
// Deno
import fs from 'node:fs';

// 3. 全局对象差异
// Node.js
const buf = Buffer.from('hello');
// Deno (需要从node:buffer导入)
import { Buffer } from 'node:buffer';
const buf = Buffer.from('hello');

// 4. __dirname / __filename
// Node.js
console.log(__dirname);
console.log(__filename);
// Deno
console.log(import.meta.dirname); // Deno 1.40+
console.log(import.meta.filename);

// 5. process对象
// Node.js
console.log(process.env.HOME);
// Deno
console.log(Deno.env.get('HOME'));
// 或兼容方式
import process from 'node:process';
console.log(process.env.HOME);

特性对比总结

特性 Node.js Deno
语言 C++/libuv Rust/Tokio
TypeScript 需要配置 原生支持
安全模型 无沙箱 默认安全,权限控制
包管理 npm/yarn/pnpm URL导入 + npm: + JSR
标准库 需要npm包 内置标准库
测试 需要Jest等 内置测试运行器
格式化 需要Prettier 内置 deno fmt
Lint 需要ESLint 内置 deno lint
生态系统 最大(npm) 较小但兼容npm
部署 PM2/Docker Deno Deploy(边缘计算)
Web标准 逐步支持 优先使用Web标准API
编译 pkg/nexe deno compile(单一可执行文件)

选择建议

flowchart TB
    START[新项目选择运行时] --> Q1{需要大量npm包?}
    Q1 -->|是| Q2{对安全性要求高?}
    Q1 -->|否| DENO_REC[推荐Deno]

    Q2 -->|是| DENO_NPM[Deno + npm兼容]
    Q2 -->|否| NODE_REC[Node.js]

    DENO_REC --> Q3{需要边缘部署?}
    Q3 -->|是| DENO_DEPLOY[Deno + Deno Deploy]
    Q3 -->|否| DENO_GENERAL[Deno]

    NODE_REC --> Q4{企业级项目?}
    Q4 -->|是| NODE_MATURE[Node.js<br>生态最成熟]
    Q4 -->|否| EITHER[两者皆可]

总结

Node.js和Deno各有优势:

  1. Node.js:生态系统最大,企业采用最广,适合需要大量第三方库的项目
  2. Deno:安全优先,开发体验好(内置TypeScript/测试/格式化/Lint),适合新项目
  3. Deno 2.0:npm兼容性大幅提升,迁移成本降低
  4. Deno Deploy:边缘计算部署方案,全球低延迟
  5. 选择建议:新项目可以优先考虑Deno,现有Node.js项目无需急于迁移

两个运行时在互相学习中共同进步——Node.js引入了权限模型(实验性),Deno提升了npm兼容性。最终受益的是开发者生态。

作者 · authorzt
发布 · date2025-07-09
篇幅 · length3.0k 字 · 7 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论