ES6
在下来的教程前,你必须先掌握一些ES6的基本技能。
ES6是当前前端的标配,具体就不讲了。
推荐阮一峰老师的《ES6 入门教程》,最好全文都能阅读一遍,能上手敲一遍代码就更好了。

其中,Generator简单了解下就行,日常工作中通常是用不到的。
Promise和async非常重要,相较之下,async优先级更高些,Promise务必掌握Promise.all、Promise.race等常用的API。
TypeScript
Deno集成了TypeScript,开箱即用,所以你也需要一些TypeScript的基础知识。
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。其设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
所以你要理解一点,Deno并不是真正的TypeScript运行时,它只是在内部将TypeScript编译成了JavaScript,运行的还是JavaScript。
TypeScript 是一种给 JavaScript 添加特性的语言扩展。增加的功能包括:
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口
- 枚举
- Mixin
- 泛型编程
- 名字空间
- 元组
你可以看看官网教程,又或者菜鸟教程。阮一峰大神也写了一本教程。
重点掌握类型、接口、枚举、泛型,可以讲,普通的接口和类型足以应付日常80%以上的开发工作了。深入的话就需要研究类型体操了,如果你做底层框架开发多半能用上。
Deno简介
Deno(/diːnoʊ/, pronounced dee-no)简单说是Node.js的替代品,是Node.js之父Ryan Dahl 为挽回Node.js的错误而开发的。
Node.js存在的问题有:
- npm包管理(npm_modules)复杂。
- 历史原因导致的api维护,比如早期变态的callback设置。
- 没有安全措施,用户只要下载了外部模块,就只好听任别人的代码在本地运行,进行各种读写操作。
- 功能不完善,导致各种工具层出不穷,比如webpack、babel等。
由于上面这些原因,Ryan Dahl 决定放弃 Node.js,从头写一个替代品,彻底解决这些问题。
Deno 这个名字就是来自 Node 的字母重新组合(Node = no + de),表示"拆除 Node.js"(de = destroy, no = Node.js)。
跟 Node.js一样,Deno 也是一个服务器运行时,但是支持多种语言,可以直接运行 JavaScript、TypeScript 和 WebAssembly 程序。
- 它内置了 V8 引擎,用来解释 JavaScript。同时,也内置了 tsc 引擎,解释 TypeScript。
- 它使用 Rust 语言开发,由于 Rust 原生支持 WebAssembly,所以它也能直接运行 WebAssembly。
- 它的异步操作不使用 libuv 这个库,而是使用 Rust 语言的 Tokio 库,来实现事件循环(event loop)。
它的架构如下图所示:

说明:
1、Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为 "安全、并发、实用",支持函数式、并发式、过程式以及面向对象的编程风格。Deno 使用 Rust 语言来封装 V8 引擎,通过 libdeno 绑定,我们就可以在 JavaScript 中调用隔离的功能。
2、Tokio 是 Rust 编程语言的异步运行时,提供异步事件驱动平台,构建快速,可靠和轻量级网络应用。利用 Rust 的所有权和并发模型确保线程安全。Tokio 构建于 Rust 之上,提供极快的性能,使其成为高性能服务器应用程序的理想选择。在 Deno 中 Tokio 用于并行执行所有的异步 IO 任务。
3、V8 是一个由 Google 开发的开源 JavaScript 引擎,用于 Google Chrome 及 Chromium 中。V8 在运行之前将JavaScript 编译成了机器代码,而非字节码或是解释执行它,以此提升性能。更进一步,使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript 程序与 V8 引擎的速度媲美二进制编译。在 Deno 中,V8 引擎用于执行 JavaScript 代码。
4、Deno与Node.js的核心模型是一致的,都是异步非阻塞I/O。所以Deno与Node.js在使用场景上基本一致,适合I/O密集型,不适用于CPU密集型。开发中要避免使用同步I/O和复杂计算场景。
优势
Deno凭什么来吸引开发者改换门庭,转投它的怀抱呢?于我而言,主要有几下几点:
1、天然支持TypeScript。如果用Node.js,需要我们手动将TS编译成JS,或者使用ts-node这种第三方工具。Deno不需要配置,开箱即用。
2、内存安全。相较于Node.js,它的性能优势并不明显,因为就是JS换了个运行环境而已,Rust并不见得比C++更高效,不过Rust会天然让它的内存安全和核心代码的健壮性更有保障些。
3、安全机制。除非明确启用,否则没有文件,网络或环境访问权限。对于运行权限的划分,能清晰知道你的程序需要拥有哪些权限,对网络资源没有信任的同学可以放心了。
4、没有历史包袱。不像Node.js一开始自定义了commonjs规范,Deno直接支持ES Modules。没有回调地狱,代码结构更清晰,利于tree shaking;也可以直接使用顶级await。
5、去中心化。不用每个工程都安装一大堆node_modules,有个线上的url就能获取代码。
6、内置浏览器API。Deno实现了fetch、FormData、WebSocket等浏览器的API,虽然被某些开发者诟病,但对前端开发者而言是真的友好,比如进行接口调用,与在浏览器里使用几乎没有区别。
7、全家桶似的服务,比如打包、格式化、代码校验,可以让你更专注业务,而不是成为某工具的配置工程师。
缺点
Deno的缺点也很明显:
1、没有针对Node.js压倒性的性能优势,所以对以上优势不感兴趣的开发者而言,就没有吸引力了。
Deno 是一个合适的异步服务器,每秒 25k 请求足以满足大多数目的,此外,由于普遍使用 Promise,Deno 有更好的尾部延迟。目前 Deno HTTP 服务器每秒处理约 25 000 个请求,最大延迟为 1.3 毫秒,与之相比,Node 程序每秒处理 34 000 个请求,最大延迟介于 2 到 300 毫秒之间。
可以看出,Deno的优势在于延迟低,但并没有压倒性优势。
2、无法完全继承Node.js的生态。虽然Deno提供了几款CDN服务,可以将旧的npm包转换为Deno可使用的格式,但并非所有npm包都能转换成功。因为Node.js仍有部分API官方没有实现,如果某个偏底层的包转换失败,所有依赖它的包都无法成功。所以,你很有可能在开发的某个环节需要造轮子。当然,这对于喜欢钻研技术的同学而言反而是个利好。(截止到2023年3月,百分之九十以上的Node.js API都已经实现了,所以大部分npm包已经可以直接使用了)
3、去中心化后,国内就没有办法像npm做镜像(其实可以配置代理,但目前国内并没有相关的服务)。官方搭建deno.land来存储github上各种资源,但目前只支持github上私有资源。想要引用私有文件,就必须自己搭建服务器,在CICD中构建的话,又有额外的技术难点需要解决。
官网
首页:deno.land/
官方教程:deno.land/manual
官方API:doc.deno.land/deno/stable
官方标准库地址:deno.land/std
第三方库地址:deno.land/x
建议先把官方教程过一遍。
安装Deno
命令安装
shell
# Shell (Mac, Linux):
$ curl -fsSL https://deno.land/install.sh | sh
# PowerShell (Windows):
$ iwr https://deno.land/install.ps1 -useb | iex
# Homebrew (Mac):
$ brew install deno
# Chocolatey (Windows):
$ choco install deno
# Scoop (Windows):
$ scoop install deno
# Build and install from source using Cargo:
$ cargo install deno --locked
下载资源安装
也可以直接在Github上下载对应的发行版本文件,如果你是windows用户,推荐使用这种。
当然,Mac与Linux也可以这样安装,但最新的Mac系统可能会因为安全权限而禁止程序使用,需要参考这里配置下。
设置环境变量
关键要设置两个环境变量,一是Deno二进制文件的目录,二是使用Deno安装的全局命令的目录。
Mac/Linux命令行操作
如果你是使用Mac/Linux命令行直接安装的,会有提示信息,让你设置:
bash
export DENO_INSTALL="$HOME/.deno"
也可以直接用下面命令:
ini
echo 'export DENO_INSTALL="$HOME/.deno"
windows版本
我的电脑 -> 属性 -> 高级系统设置 -> 高级 -> 环境变量设置,上面用户变量和系统变量都可以配置。
比如把下载的deno.exe放在c:/bin目录下:

那就需要设置在path的环境变量里添加一条:

另一个环境变量就是 DENO_INSTALL了,与Linux一样,通常是$HOME/.deno,比如我的是C:\Users\Administrator.deno,把它的bin目录添加到path中。

验证
打开命令窗口:
css
deno --version
成功了后能看到:

如果失败,极可能是环境变量没有生效,windows用户建议重启电脑。
升级
deno upgrade
Deno基础
下面这些Deno基础需要了解下。
安全机制
Deno 具有安全控制,默认情况下脚本不具有读写权限。如果脚本未授权,就读写文件系统或网络,会报错。
必须使用参数,显式打开权限才可以。
css
--allow-read:打开读权限,可以指定可读的目录,比如--allow-read=/temp。
--allow-write:打开写权限。
--allow-net=google.com:允许网络通信,可以指定可请求的域,比如--allow-net=google.com。
--allow-env:允许读取环境变量。
--allow-run:允许运行子进程。
要使用不稳定的新特性,需要这样:
css
--unstable
还有新加的--allow-ffi
和--allow-hrtime
,一般用不到,这里不再赘述,请情参考官方文档。
运行
使用deno run来运行程序,当然,一般需要加上上面的安全机制。
- 如果你的程序什么权限都没使用,只是下面这种:
ts
console.log('app start');
那么直接用deno run main.ts
就可以了。注意,我们推荐使用ts,但也可以运行js。
- 如果需要读取文件,比如:
ts
const decoder = new TextDecoder("utf-8");
const data = await Deno.readFile("hello.txt");
console.log(decoder.decode(data));
自然得这样:deno run --allow-read main.ts
- 如果需要网络,比如开启一个tcp:
ts
/**
* echo_server.ts
*/
import { copy } from "https://deno.land/std@0.106.0/io/util.ts";
const listener = Deno.listen({ port: 8080 });
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
copy(conn, conn).finally(() => conn.close());
}
你必须:deno run --allow-net main.ts
- 如果你用了不稳定的API,那么也需要显示指定
--unstable
。 - 如果懒得管理权限,则使用
-A
(deno run -A main.ts
),那就后果自负了。
更新模块
默认代码中引用中的ts或js文件,会缓存到本地。如果你引用的文件url中没有带版本号,那么就是最新版的代码。这时,你想要更新代码,那么你需要这样操作:
css
deno cache --reload my_module.ts
更新线上与本地文件:
ini
deno cache --reload=https://deno.land/std@0.106.0 my_module.ts
更新线上某个文件与某文件夹:
ini
deno cache --reload=https://deno.land/std@0.106.0/fs/copy.ts,https://deno.land/std@0.106
锁定文件校验
如果你在开发一个底层库或框架供他人使用,那么推荐创建一个deps.ts文件,来管理外部引用的文件。其实就是起到类似package-lock.json的作用,管理引用的文件与版本。
ts
// src/deps.ts
export { xyz } from "https://unpkg.com/xyz-lib@v0.9.0/lib.ts";
然后用deno cache 生成一个lock.json文件:
ts
# Create/update the lock file "lock.json".
deno cache --lock=lock.json --lock-write src/deps.ts
另一台机器克隆下代码,运行后:
ts
# Download the project's dependencies into the machine's cache, integrity
# checking each resource.
deno cache --reload --lock=lock.json src/deps.ts
# Done! You can proceed safely.
deno test --allow-read src
目前发现的问题,如果引用了第三方CDN的文件,同样的文件可能生成不同的lock.json
。而新版本Deno现在默认会生成deno.lock
文件。
导入地图
可以使用带有--import-map=<FILE>CLI
标志的导入映射,这样开发时使用体验类似于Node.js了。
不过如果是发布给别人使用的模块,不建议用它,因为这需要别人也把你的map文件复制过来。
例:import_map.json
json
{
"imports": {
"fmt/": "https://deno.land/std@0.106.0/fmt/"
}
}
main.ts:
ts
import { red } from "fmt/colors.ts";
然后:
arduino
deno run --import-map=import_map.json main.ts
建议在上层应用开发时,所有的第三方引用都在import_map.json里维护,这样好管理依赖的版本。
来自CDN的包
目前,推荐将共享的代码发布在deno.land/x/上,它的发布也很简单,在你的GitHub仓库里配置一个hook,就可以了。

详见这篇文章《手把手教你发布一个Deno模块》
esm.sh
esm.sh是个转换npm包为Deno使用的线上地址,它使用esbuild进行转换,速度很快。比如我们要使用React,可以这样直接使用:
javascript
import React from "https://esm.sh/react";
当然也支持版本号:
javascript
import React from "https://esm.sh/react@17.0.2";
Skypack
Skypack也是类似:
javascript
import React from "https://cdn.skypack.dev/react";
直接使用npm包
Deno从1.28版本起,可以这样直接利用npm的包:
ts
import chalk from "npm:chalk@5";
引用规则:
lua
npm:<package-name>[@<version-requirement>][/<sub-path>]
以下是另一个样例:
ts
// main.ts
// @deno-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
const app = express();
app.get("/", (req, res) => {
res.send("Hello World");
});
app.listen(3000);
console.log("listening on http://localhost:3000/");
如果在国内下载npm的网速较慢,可以使用环境变量NPM_CONFIG_REGISTRY
来设置npm的镜像:
ini
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com/ deno run -A main.ts
安装脚本
使用deno install可以方便地安装一个脚本,类似于npm i -g xxx模块。
bash
$ deno install --allow-net --allow-read https://deno.land/std@0.106.0/http/file_server.ts
[1/1] Compiling https://deno.land/std@0.106.0/http/file_server.ts
✅ Successfully installed file_server.
/Users/deno/.deno/bin/file_server
之后就能使用file_server命令了。当然,需要将打印信息里这个bin目录添加到环境变量里,前文提过,这里不再赘述。
代码风格
格式化
使用deno fmt。它会将代码格式化为官方推荐的样子,其实起到的类似eslint --fix的效果。vscode中安装了Deno插件后,可以很容易开启自动格式化。
bash
# format all JS/TS files in the current directory and subdirectories
deno fmt
# format specific files
deno fmt myfile1.ts myfile2.ts
# check if all the JS/TS files in the current directory and subdirectories are formatted
deno fmt --check
# format stdin and write to stdout
cat file.ts | deno fmt -
代码校验
使用deno lint。有了它,就不必再使用eslint了。
shell
# lint all JS/TS files in the current directory and subdirectories
$ deno lint
# lint specific files
$ deno lint myfile1.ts myfile2.ts
# print result as JSON
$ deno lint --json
# read from stdin
$ cat file.ts | deno lint -
编码风格指南
参见官方指南,举几条对我们有用的:
- 使用 TypeScript 而不是 JavaScript
- 在文件名中使用下划线,而不是破折号
- 不要使用文件名index.ts/ index.js。入口文件使用mod.ts/mod.js
- 导出的函数:最多 2 个参数,将其余的放入选项对象中
- 顶级函数不应使用箭头语法。顶级函数应该使用function关键字。箭头语法应该仅限于闭包
调试
像Node.js一样,Deno程序也可以在chrome浏览器中调试,已经是很古老的调试方案了,具体就不介绍了。
这里主要说下vscode中调试。
需要在项目根目录下配置.vscode/launch.json
文件,内容大概如下:
kotlin
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "deno",
"request": "launch",
"type": "node",
"program": "src/main.ts", // 具体文件路径
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": [
"run",
"--unstable",
"--inspect",
"--allow-all"
],
"attachSimplePort": 9229
}
]
}
详情可以参考这里[6]。
在文件中打好断点,以调试方式运行就可以了。

deno info
详见这篇:Deno编译问题
开发环境
软件推荐
推荐使用vscode,有钱任性的用webstorm也可以。
git安装
代码存储当然要使用git。
对git不熟悉的同学,参看廖雪峰老师的《Git 教程》。
git的提交信息要遵循 Angular 团队提议的《AngularJS Git Commit Message Conventions》:
<type>(<scope>)
:<subject>
必须填写空一行
<body>
选填空一行
<footer>
选填
-
type 类型只能从以下列表选择:
- build:影响生成系统或外部依赖性的更改
- ci: 更改 CI 配置文件和脚本
- feat: 新功能(feature)
- fix: 修补 bug
- perf: 提高性能的代码更改
- docs: 文档(documentation)
- style: 不影响代码含义的更改(不影响代码运行的变动)
- refactor: 代码修改既不修复错误,也不添加特征(即不是新增功能,也不是修改 bug 的代码变动)
- test: 添加缺失测试或纠正现有测试
- revert: 撤回
- scope 影响范围
- subject 是 commit 目的的简短描述,不超过 72 个字符
- body Body 部分是对本次 commit 的详细描述,可以分成多行
- footer 不兼容变动主要及一些额外说明
扩展安装
在扩展商店中查找deno,安装,然后视情况选择是全局启用,还是工作区使用。

配置文件
当前工程目录下,新建.vscode文件夹,编辑settings.json:
json
{
"deno.enable": true,
"deno.suggest.imports.hosts": {
"https://deno.land": true
},
"deno.lint": true,
"deno.suggest.autoImports": true,
}
开启了deno.lint之后,如果代码校验问题,会出现下方的问题里:

如果用到了importMap和tsconfig.json(官方推荐改用deno.json或deno.jsonc),也要对应配置:
json
{
"deno.importMap": "import_map.json",
"deno.config": "deno.json"
}
还有其它配置,视情况自行启用。
自动格式化
打开一个ts文件,比如内容是这样:
ts
console.log(11)
从deno默认的代码规范上,它是不合格的,因为丢失了最后的分号(;)。
右键菜单,找到使用...格式化文档:


配置默认为Deno。
回到页面时,保存代码,会看到分号就自动加上了。
这对保障我们代码统一的格式、风格是有益的。通常情况下,我们用默认的风格就足够了,不需要一个团队为是用双引号还是单引号,结尾要不要加分号而纠结。
安装脚本
git hook
在工程根目录下执行:
arduino
deno run --allow-write https://deno.land/x/jw_cli@v0.5.0/cli/git_hook.ts
这个脚本其实就是让你每次git commit时触发deno lint命令。
版本升级推送
全局安装:
arduino
deno install --allow-read --allow-write --allow-run --unstable -n tag -f https://deno.land/x/jw_cli@v0.5.1/cli/tag/mod.ts
版本号更新
在项目根目录下执行patch/minor/major
进行版本号的变更,逻辑与Node.js的npm version
命令一致,它会更新deno.json[c]
文件中版本号。
tag patch
tag minor
tag major
它会同时更新根目录下的deno.json[c]
文件和README.md
,如果后者有使用deno.json[c]
中配置的name
,将会对应替换。
比如原来REAME.md中有段是这样的:
ts
import { Body } from "https://deno.land/x/oak_nest@v1.13.19/mod.ts";
deno.json[c]
中name为oak_nest
,那么这里的oak_nest@v1.13.19
都会对应替换为新的版本。
版本号不以v开头
假设你推送的tag版本号不想以v开头,那么可以添加一个参数-L或者--local:
tag patch -L
添加自定义信息
打标签时默认提交信息是版本号,如果想自定义信息,可以使用-M或者--msg:
arduino
tag minor -M "feat: change some"
更新所有目录的README.md文件
如果想要更新所有目录的README.md文件,可以使用-D或者--deep:
tag -D