对 Deno 感兴趣,想尝尝鲜或快速入门 Deno 的小伙伴看过来,本文将从七个方面入手,带你一步步学习 Deno 的相关知识,详细的内容大纲如下图所示:
自从 Deno 1.0 正式发布之后,Deno 的热度渐渐褪去。但近期 Deno 团队计划 删除所有内部代码构建时的 TS 类型检查与捆绑,又把 Deno 再一次带入大家的视野。
下面我们来通过知乎上 justjavac(迷渡大大) 一个高赞的回答来大致了解一下其主要原因。
来自 天猪 的提问:如何评价 deno 计划把一些内部模块从 ts 改回 js?
来自 justjavac 的高赞回答:
先说原因:根本原因是 ts 无法为 Deno runtime 生成高性能的 js 代码。
首先需要澄清一个误区:Deno 并没有放弃 TypeScript,Deno 依然是一个安全的 TS/JS runtime。
编译速度慢是一个方面,但不是根本原因。rustc 的编译速度也很慢,但是生成的代码质量很高。tsc 确实也慢,如果 tsc 能够生成高性能的 js 代码,那么这点构建的时间成本是可以接受的。但是目前来看 ts 无法为 Deno runtime 生成高性能的 js 代码。或者说,Deno 团队没有找到 ts 生成高性能 js 代码的方式。
Deno 使用最新版 ts 和 v8,而 ts 和 v8 的实现目标都是 stage3,理论上 ts 只要简单的做类型擦除就可以直接运行在 v8 上。最初 Deno 团队也是这么想的。但是实际使用 ts 的情况却不是这么理想。
此处省略好多个字,这里就不继续“搬运”了,感兴趣的小伙伴可以仔细阅读一下原文,文中 justjavac 详细介绍了来龙去脉。
原文地址:https://www.zhihu.com/questio…
一、Deno 简介
Deno 是一个 JavaScript/TypeScript 的运行时,默认使用安全环境执行代码,有着卓越的开发体验。
(图片来源:https://deno.land/artwork)
Deno 是 Node.js 之父 Ryan Dahl 的另一个大作,它跟 Node.js 的关系是这样的:
"node".split("").sort().join(""); // Output: deno
那么实际上 Deno 与 Node.js 之间有什么区别呢?这里阿宝哥就不展开介绍了,感兴趣的小伙伴可以阅读“Deno 正式发布,彻底弄明白和 node 的区别”这篇文章。
下面我们步入正题,开始进入 Deno 的世界,Deno 含有以下功能特性:
- 默认安全,外部代码没有文件系统、网络、环境的访问权限,除非显式开启。
- 支持开箱即用的 TypeScript 的环境。
- 只分发一个独立的可执行文件(deno)。
- 有着内建的工具箱,比如一个依赖信息查看器(deno info)和一个代码格式化工具(deno fmt)。
- 有一组经过审计的标准模块,保证能在 Deno 上工作。
- 脚本代码能被打包为一个单独的 JavaScript 文件。
Deno 是一个跨平台的运行时,即基于 Google V8 引擎的运行时环境,该运行时环境是使用 Rust 语言开发的,并使用 Tokio 库来构建事件循环系统。Deno 建立在 V8、Rust 和 Tokio 的基础上,它的架构如下:
(图片来源:https://deno.land/manual/cont…)
1.1 Rust
Rust 是由 Mozilla 主导开发的通用、编译型编程语言。设计准则为“安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。Deno 使用 Rust 语言来封装 V8 引擎,通过 libdeno
绑定,我们就可以在 JavaScript 中调用隔离的功能。
1.2 Tokio
Tokio 是 Rust 编程语言的异步运行时,提供异步事件驱动平台,构建快速,可靠和轻量级网络应用。利用 Rust 的所有权和并发模型确保线程安全。Tokio 构建于 Rust 之上,提供极快的性能,使其成为高性能服务器应用程序的理想选择。在 Deno 中 Tokio 用于并行执行所有的异步 IO 任务。
1.3 V8
V8 是一个由 Google 开发的开源 JavaScript 引擎,用于 Google Chrome 及 Chromium 中。V8 在运行之前将 JavaScript 编译成了机器代码,而非字节码或是解释执行它,以此提升性能。更进一步,使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript 程序与 V8 引擎的速度媲美二进制编译。在 Deno 中,V8 引擎用于执行 JavaScript 代码。
二、安装 Deno
Deno 能够在 Mac、Linux 和 Windows 上运行。Deno 是一个单独的可执行文件,它没有额外的依赖。你可以通过以下方式来安装它:
- 使用 Shell(Mac 和 Linux):
$ curl -fsSL https://deno.land/x/install/install.sh | sh
- 使用 PowerShell(Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
- 使用 Scoop(Windows):
scoop install deno
- 使用 Chocolatey(Windows):
choco install deno
- 使用 Homebrew(Mac):
brew install deno
- 使用 Cargo(Windows,Mac,Linux):
cargo install deno
Deno 也可以手动安装,只需从 github.com/denoland/deno/releases 下载一个 zip 文件。它仅包含一个单独的可执行文件。在 Mac 和 Linux 上,你需要为它设置执行权限。当你成功安装之后,可以通过执行 deno --version
命令来查看已安装的 Deno 版本:
$ deno --version
deno 1.1.2
v8 8.5.216
typescript 3.9.2
2.1 deno_install
在安装过程中,如果遇到问题的话,大家可以试试 justjavac(迷渡)大神提供的安装脚本 —— deno_install。该脚本通过单行命令将 Deno 安装到系统中(国内加速)。
2.1.1 安装最新版
使用 Shell:
$ curl -fsSL https://x.deno.js.cn/install.sh | sh
使用 PowerShell:
$ iwr https://x.deno.js.cn/install.ps1 -useb -outf install.ps1; .\install.ps1
# iwr https://x.deno.js.cn/install.ps1 -useb | iex
2.1.2 安装某个特定版本
使用 Shell:
$ curl -fsSL https://x.deno.js.cn/install.sh | sh -s v0.41.0
使用 PowerShell:
$ iwr https://x.deno.js.cn/install.ps1 -useb -outf install.ps1; .\install.ps1 v0.41.0
更多详细的信息可以浏览 x.deno.js.cn 站点。
2.2 deno-cli
deno-cli 命令行界面提供了一组集成功能,让你可以沉浸在 Deno 的专有开发环境中。以下是 Deno 1.1.2 版本支持的所有子命令:
SUBCOMMANDS:
bundle Bundle module and dependencies into single file
cache Cache the dependencies
completions Generate shell completions
doc Show documentation for a module
eval Eval script
fmt Format source files
help Prints this message or the help of the given subcommand(s)
info Show info about cache or info related to source file
install Install script as an executable
lint Lint source files
repl Read Eval Print Loop
run Run a program given a filename or url to the module. Use '-' as a filename
to read from stdin.
test Run tests
types Print runtime TypeScript declarations
upgrade Upgrade deno executable to given version
2.3 REPL
在命令中输入 deno 命令,你就会启动一个 REPL(Read-Execute-Print-Loop):
$ deno
Deno 1.1.2
exit using ctrl+d or close()
> 0.1 + 0.2
0.30000000000000004
> const name = "阿宝哥";
undefined
> console.log(name);
阿宝哥
undefined
“读取 - 求值 - 输出”循环(英语:Read-Eval-Print Loop,简称 REPL),也被称做交互式顶层构件(英语:interactive toplevel),是一个简单的,交互式的编程环境。这个词常常用于指代一个 Lisp 的交互式开发环境,也能指代命令行的模式。
2.4 VSCode Deno extension
相信很多小伙伴都在使用 VSCode IDE 进行 Web 开发,对于 Deno 的开发者来说,一定不能错过 Deno 官方开发的 Visual Studio Code Deno extension 扩展。
2.4.1 未安装 Deno extension
如果我们写 from "./hello.ts"
这样的语句,在 VSCode 中将会出现波浪号的错误信息。因为默认情况下,TypeScript 项目不需要添加 .ts
扩展名。
ts(2691): An import path cannot end with a ‘.ts’ extension. Consider importing ‘./hello’ instead.
Deno 允许从 URL 中导入模块,但是 TypeScript 并不支持从 URL 中导入模块。
ts(2307): Cannot find module ‘https://deno.land/x/std/log/mod’.
2.4.2 已安装 Deno extension
Deno 将远程导入(imports)缓存在 $DENO_DIR
环境变量指定的特殊目录中。如果未指定 $DENO_DIR
,则默认为系统的缓存目录。
该插件可以将远程导入(remote imports)解析为本地路径。
(本章节图片来源:https://marketplace.visualstu…)
了解 VSCode Deno extension 更多的详细信息,可以阅读 justjavac(迷渡) 大佬 我为 VS Code 开发了一个 Deno 插件 这篇文章。
三、Deno 初体验
3.1 Welcome 示例
相信一些读者安装完 Deno 已经迫不及待了,现在我们立马来体验一下 Deno 应用程序。首先打开你熟悉的命令行,然后在命令行输入以下命令:
$ deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno ????
通过观察以上输出,我们可以知道当运行 deno run https://deno.land/std/examples/welcome.ts
命令之后,Deno 会先从 https://deno.land/std/examples/welcome.ts
URL 地址下载 welcome.ts
文件,该文件的内容是:
console.log("Welcome to Deno ????");
当文件下载成功后,Deno 会对 welcome.ts
文件进行编译,即编译成 welcome.ts.js
文件,然后再通过 V8 引擎来执行编译生成的 JavaScript 文件。需要注意的是,如果你在命令行重新运行上述命令,则会执行缓存中已生成的文件,并不会再次从网上下载 welcome.ts
文件。
$ deno run https://deno.land/std/examples/welcome.ts
Welcome to Deno ????
那如何证明再次执行上述命令时,Deno 会优先执行缓存中编译生成的 JavaScript 文件呢?这里我们要先介绍一下 deno info
命令,该命令用于显示有关缓存或源文件相关的信息:
$ deno info
DENO_DIR location: "/Users/fer/Library/Caches/deno"
Remote modules cache: "/Users/fer/Library/Caches/deno/deps"
TypeScript compiler cache: "/Users/fer/Library/Caches/deno/gen"
在上述的输出信息中,我们看到了 TypeScript compiler cache 这行记录,很明显这是 TypeScript 编译器缓存的目录,进入该目录后,通过一层层的查找,我们最终在 examples
目录下找到了 welcome.ts.js
文件:
➜ examples ls
welcome.ts.buildinfo welcome.ts.js.map
welcome.ts.js welcome.ts.meta
打开目录中 welcome.ts.js
文件,我们可以看到以下内容:
"use strict";
console.log("Welcome to Deno ????");
//#sourceMappingURL=data:application/json;base64,eyJ2Z...
下面我们来修改该文件,在文件中添加一行输出信息 console.log("Hello Semlinker, from Cache");
,具体如下:
"use strict";
console.log("Hello Semlinker, from Cache");
console.log("Welcome to Deno ????");
//#sourceMappingURL=data:application/json;base64,eyJ2Z...
接着我们在命令行中重新执行以下命令:
$ deno run https://deno.land/std/examples/welcome.ts
Hello Semlinker, from Cache
Welcome to Deno ????
那么现在问题又来了,如何强制刷新缓存,即重新编译 TypeScript 代码呢?针对这个问题,在运行 deno run
命令时,我们需要添加 --reload
标志,来告诉 Deno 需要重新刷新指定文件:
$ deno run --reload https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno ????
除了 --reload
标志之外,Deno run 命令还支持很多其他的标志,感兴趣的读者可以运行 deno run --help
命令来查看更多的信息。
3.2 TCP Echo Server
前面我们已经介绍了如何运行官方的 welcome 示例,下面我们来介绍如何使用 Deno 创建一个简单的 TCP echo 服务器。首先我们创建一个 learn-deno 项目,然后在该项目下新建一个 quickstart 目录,接着新建一个 echo_server.ts
文件并输入以下代码:
const listener = Deno.listen({port: 8080});
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {Deno.copy(conn, conn);
}
for await…of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如 arguments 或者 NodeList),TypedArray,Map,Set 和自定义的异步或者同步可迭代对象。
for await…of 的语法如下:
for await (variable of iterable) {statement}
输入完以上代码之后,相信很多读者会跟我一样,直接在命令行运行以下命令:
➜ quickstart deno run ./echo_server.ts
Compile file:///Users/fer/LearnProjects/learn-deno/quickstart/echo_server.ts
error: Uncaught PermissionDenied: network access to "0.0.0.0:8080", run again with the --allow-net flag
at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
at Object.sendSync ($deno$/ops/dispatch_json.ts:72:10)
at Object.listen ($deno$/ops/net.ts:51:10)
at Object.listen ($deno$/net.ts:152:22)
at file:///Users/fer/LearnProjects/learn-deno/quickstart/echo_server.ts:1:23
很明显是权限错误,从错误信息中,Deno 告诉我们需要设置 --allow-net
标志,以允许网络访问。为什么会这样呢?这是因为 Deno 是一个 JavaScript/TypeScript 的运行时,默认使用安全环境执行代码。下面我们添加 --allow-net
标志,然后再次运行 echo_server.ts
文件:
➜ quickstart deno run --allow-net ./echo_server.ts
listening on 0.0.0.0:8080
当服务器成功运行之后,我们使用 nc
命令来测试一下服务器的功能:
➜ ~ nc localhost 8080
hell semlinker
hell semlinker
介绍完如何使用 Deno 创建一个简单的 TCP echo 服务器,我们再来介绍一下如何使用 Deno 创建一个简单的 HTTP 服务器。
3.3 HTTP Server
与 TCP Server 一样,在 quickstart 目录下,我们新建一个 http_server.ts
文件并输入以下内容:
import {serve} from "https://deno.land/std@v0.50.0/http/server.ts";
const PORT = 8080;
const s = serve({port: PORT});
console.log(` Listening on <http://localhost>:${PORT}/`);
for await (const req of s) {req.respond({ body: "Hello Semlinker\\n"});
}
友情提示:在实际开发过程中,你可以从 https://deno.land/std 地址获取所需的标准库版本。示例中我们显式指定了版本,当然你也可以不指定版本,比如这样:https://deno.land/std/http/se…。
在上述代码中,我们导入了 Deno 标准库 http 模块中 serve 函数,然后使用该函数快速创建 HTTP 服务器,该函数的定义如下:
// https://github.com/denoland/deno/blob/master/std/http/server.ts
export function serve(addr: string | HTTPOptions): Server {if (typeof addr === "string") {const [hostname, port] = addr.split(":");
addr = {hostname, port: Number(port) };
}
const listener = Deno.listen(addr);
return new Server(listener);
}
serve 函数接收一个参数,其类型是 string | HTTPOptions
,其中 HTTPOptions 接口的定义如下:
/** Options for creating an HTTP server. */
export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;
export interface ListenOptions {
/** The port to listen on. */
port: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `0.0.0.0`. */
hostname?: string;
}
当输入的参数类型是字符串时,serve 函数会使用 :
冒号对字符串进行切割,获取 hostname
和 port
,然后包装成对象赋值给 addr 参数,接着使用 addr 参数继续调用 listen
函数进一步创建 listener
对象,最终调用 new Server(listener)
创建 HTTP 服务器。
创建完 HTTP 服务器,我们来启动该服务器,打开命令行输入以下命令:
➜ quickstart deno run --allow-net ./http_server.ts
Compile file:///Users/fer/LearnProjects/learn-deno/quickstart/http_server.ts
Listening on <http://localhost>:8080/
接着打开浏览器,在地址栏上输入 http://localhost:8080/ 地址,之后在当前页面中会看到以下内容:
Hello Semlinker\n
3.4 Cat 命令
介绍完如何使用 Deno 搭建一个 HTTP 服务器,接下来我们再来介绍如何使用 Deno 开发一个简单的 cat
命令。在 Linux 系统中,cat
命令用于连接文件并打印到标准输出设备上。现在我们在 quickstart 目录下,新建一个 cat.ts
文件并输入以下内容:
for (let i = 0; i < Deno.args.length; i++) {let filename = Deno.args[i];
let file = await Deno.open(filename);
await Deno.copy(file, Deno.stdout);
file.close();}
在以上代码中,我们使用了 Deno 命名空间下的 3 个 API,它们的作用如下:
-
Deno.args
:获取脚本接收的参数; -
Deno.open
:用于打开文件,返回一个 Promise 对象,resolve 后返回一个 Deno.File 对象; -
Deno.copy
:用于执行拷贝操作,实现从源到目标的拷贝。当遇到 EOF 或发生错误时,会停止拷贝操作。调用该方法后,也会返回一个 Promise 对象,resolve 后会返回已复制的字节数。
由于 Deno 是一个 JavaScript/TypeScript 的运行时,默认使用安全环境执行代码。因此在运行 cat.ts
脚本时,我们要设置 --allow-read
标志,已允许文件系统读访问。即具体的脚本如下所示:
➜ quickstart deno run --allow-read cat.ts echo_server.ts
以上命令成功运行后,控制台会输出以下结果:
const listener = Deno.listen({port: 8080});
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {Deno.copy(conn, conn);
}
快速体验完 Deno,要进行实际项目开发,我们还得了解一下如何调试 Deno 应用程序,所以下面我们将介绍如何使用 Chrome Devtools 和 VSCode 调试 Deno 应用程序。
四、调试 Deno
Deno 支持 V8 Inspector Protocol。使用 Chrome Devtools 或其他支持该协议的客户端(比如 VSCode)能够调试 Deno 程序。要启用调试功能,用 --inspect
或 --inspect-brk
选项运行 Deno,对应的选项描述如下:
--inspect=<HOST:PORT>
activate inspector on host:port (default: 127.0.0.1:9229)
--inspect-brk=<HOST:PORT>
activate inspector on host:port and break at start of user script
--inspect
选项允许在任何时间点连接调试器,而 --inspect-brk
选项会等待调试器连接,在第一行代码处暂停执行。
4.1 Chrome Devtools
让我们用 Chrome 开发者工具来调试一个简单的程序,我们将使用来自 std
的 file_server.ts,这是一个简单的静态文件服务。
使用 --inspect-brk
选项,在第一行代码处暂停执行。
$ deno run --inspect-brk --allow-read --allow-net https://deno.land/std@v0.50.0/http/file_server.ts
Debugger listening on ws://127.0.0.1:9229/ws/1e82c406-85a9-44ab-86b6-7341583480b1
Download https://deno.land/std@v0.50.0/http/file_server.ts
Compile https://deno.land/std@v0.50.0/http/file_server.ts
...
打开 chrome://inspect
,点击 Target 旁边的 Inspect
。
进一步了解更详细的调试说明,可访问 https://deno.land/manual/tool… URL 地址。
4.2 VSCode
Deno 可以在 VSCode 中调试。插件的官方支持正在开发中 https://github.com/denoland/v…,当然我们也可以通过手动提供 launch.json
配置,来连接调试器:
{
"version": "0.2.0",
"configurations": [
{
"name": "Deno",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "deno",
"runtimeArgs": ["run", "--inspect-brk", "-A", "<entry_point>"],
"port": 9229
}
]
}
注意:将 <entry_point>
替换为实际的脚本名称。
下面让我们来尝试一下调试本地源文件,创建 server.ts
:
import {serve} from "https://deno.land/std@v0.50.0/http/server.ts";
const s = serve({port: 8000});
console.log("http://localhost:8000/");
for await (const req of s) {req.respond({ body: "Hello World\n"});
}
将 <entry_point>
改为 server.ts
,然后运行。
(图片来源:https://deno.land/manual/tool…)
(图片来源:https://deno.land/manual/tool…)
五、Deno Web API
Deno 还支持 W3C 标准规范,因此像 fetch
、setTimeout
、Blob
和 Worker
等 API 都可以被直接使用。目前 Deno 已实现了以下 Web APIs:
- Blob:用于表示二进制数据;
- Console:用于输出日志信息;
- CustomEvent,EventTarget 和 EventListener:用于处理 DOM 事件,需要注意的是,由于 Deno 中没有 DOM 层次结构,因此没有用于事件冒泡 / 捕获的树;
- fetch, Request, Response, Body 和 Headers:现代基于 Promise 的 HTTP 请求 API;
-
FormData:用于处理
multipart/form-data
表单数据; - Performance: 高精度获取当前时间;
- setTimeout, setInterval, clearTimeout 和 clearInterval:管理和调度定时任务;
- Stream:用于创建、组合和使用数据流的 Stream API;
- URL 和 URLSearchParams: 用于构造和解析 URLs;
- Worker:在单独的线程中执行其他代码,需要注意的是,不支持 Blob URLs,且无法转让对象所有权,传递的数据使用 JSON 序列化,而不是结构化克隆。
出于篇幅考虑考虑,阿宝哥不打算介绍上面所有 Web APIs,只打算简单介绍一下 Blob API 和 Worker API。
5.1 Blob API
Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。在 JavaScript 中 Blob 类型的对象表示不可变的类似文件对象的原始数据。
Blob
由一个可选的字符串 type
(通常是 MIME 类型)和 blobParts
组成:
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型,是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
常见的 MIME 类型有:超文本标记语言文本 .html text/html、PNG 图像 .png image/png、普通文本 .txt text/plain 等。
5.1.1 构造函数
Blob 构造函数的语法为:
var aBlob = new Blob(blobParts, options);
相关的参数说明如下:
- blobParts:它是一个由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等对象构成的数组。DOMStrings 会被编码为 UTF-8。
-
options:一个可选的对象,包含以下两个属性:
- type —— 默认值为
""
,它代表了将会被放入到 blob 中的数组内容的 MIME 类型。 - endings —— 默认值为
"transparent"
,用于指定包含行结束符\n
的字符串如何被写入。它是以下两个值中的一个:"native"
,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者"transparent"
,代表会保持 blob 中保存的结束符不变。
- type —— 默认值为
5.1.2 属性
前面我们已经知道 Blob 对象包含两个属性:
- size(只读):表示
Blob
对象中所包含数据的大小(以字节为单位)。 - type(只读):一个字符串,表明该
Blob
对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。
5.1.3 方法
- slice([start[, end[, contentType]]]):返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。
- stream():返回一个能读取 blob 内容的
ReadableStream
。 - text():返回一个 Promise 对象且包含 blob 所有内容的 UTF-8 格式的
USVString
。 - arrayBuffer():返回一个 Promise 对象且包含 blob 所有内容的二进制格式的
ArrayBuffer
。
这里我们需要注意的是,Blob
对象是不可改变的。我们不能直接在一个 Blob 中更改数据,但是我们可以对一个 Blob 进行分割,从其中创建新的 Blob 对象,将它们混合到一个新的 Blob 中。这种行为类似于 JavaScript 字符串:我们无法更改字符串中的字符,但可以创建新的更正后的字符串。
5.1.4 使用示例
let myBlobParts = ["<html><h2>Hello Semlinker</h2></html>"];
let myBlob = new Blob(myBlobParts, {
type: "text/html",
ending: "transparent",
});
console.log(myBlob.size + "bytes size");
console.log(myBlob.type + "is the type");
以上代码使用 deno run deno-blob.ts
命令运行后,命令行会输出以下结果:
37 bytes size
text/html is the type
5.2 Worker API
Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。
在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,可以在独立线程中处理一些计算密集型或高延迟的任务,从而允许主线程(通常是 UI 线程)不会因此被阻塞或拖慢。
(图片来源:https://viblo.asia/p/simple-w…)
Worker() 构造函数创建一个 Worker 对象,该对象执行指定的 URL 脚本。这个脚本必须遵守同源策略。如果违反同源策略,则会抛出一个 SECURITY_ERR 类型的 DOMException。
5.2.1 Worker 构造函数
Worker 构造函数的语法为:
const myWorker = new Worker(aURL, options);
相关的参数说明如下:
- aURL:是一个 DOMString 表示 worker 将执行的脚本的 URL。它必须遵守同源策略。
-
options(可选):包含可在创建对象实例时设置的选项属性的对象。可用属性如下:
- type:用以指定 Worker 类型的 DOMString 值. 该值可以是 classic 或 module。如果未指定,将使用默认值 classic。
- credentials:用以指定 worker 凭证的 DOMString 值。该值可以是 omit,same-origin 或 include。如果未指定,或者 type 是 classic,将使用默认值 omit (不要求凭证)。
- name:在 DedicatedWorkerGlobalScope 的情况下,用来表示 Worker 的 scope 的一个 DOMString 值,主要用于调试目的。
需要注意的是,在创建 Web Worker 的时候,可能会出现以下异常:
- 当 document 不被允许启动 worker 的时候,将抛出一个 SecurityError 异常。比如:如果提供的 aURL 有语法错误,或者与同源策略相冲突(跨域访问)。
- 如果 worker 的 MIME 类型不正确,将抛出一个 NetworkError 异常。worker 的 MIME 类型必须是
text/javascript
。 - 如果 aURL 无法被解析(格式错误),将抛出一个 SyntaxError 异常。
5.1.2 使用示例
main.ts
const worker = new Worker(new URL("worker.ts", import.meta.url).href, {type: "module",});
worker.onmessage = (e: MessageEvent) => {console.log(`Main: Received msg from deno worker - ${e.data}`);
};
worker.postMessage("Hello Deno");
worker.ts
self.onmessage = (e: MessageEvent) => {console.log(`Worker: Received from main - ${e.data}`);
self.postMessage("Hello Semlinker");
};
以上代码使用 deno run --allow-read main.ts
命令运行后,命令行会输出以下结果:
Worker: Received from main - Hello Deno
Main: Received msg from deno worker - Hello Semlinker
六、Deno Web 开发
相信接触过 Node.js 的读者对 Express、Hapi、Koa 这些 Web 应用开发框架都不会陌生,在 Deno 平台中如果你也想做 Web 应用开发,可以考虑直接使用以下现成的框架:
- abc:A better Deno framework to create web application。
- deno-drash:A REST microframework for Deno with zero dependencies。
- deno-express:Node Express way for Deno。
- oak:A middleware framework for Deno’s net server ????。
- pogo:Server framework for Deno。
- servest:????A progressive http server for Deno????。
写作本文时,目前 Star 数最高的项目是 Oak,下面我们来简单介绍一下 Oak:
A middleware framework for Deno’s http server, including a router middleware.
This middleware framework is inspired by Koa and middleware router inspired by koa-router.
很显然 Oak 的的灵感来自于 Koa,而路由中间件的灵感来源于 koa-router 这个库。如果你以前使用过 Koa 的话,相信你会很容易上手 Oak。不信的话,我们来看个示例:
import {Application} from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use((ctx) => {ctx.response.body = "Hello Semlinker!";});
await app.listen({port: 8000});
以上示例对于每个 HTTP 请求,都会响应 “Hello Semlinker!”。只有一个中间件是不是感觉太 easy 了,下面我们来看一个更复杂的示例(使用多个中间件):
import {Application} from "https://deno.land/x/oak/mod.ts";
const app = new Application();
// Logger
app.use(async (ctx, next) => {await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
// Timing
app.use(async (ctx, next) => {const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
// Hello World!
app.use((ctx) => {ctx.response.body = "Hello World!";});
await app.listen({port: 8000});
为了更好地理解 Oak 中间件流程控制,我们来一起回顾一下 Koa 大名鼎鼎的 “洋葱模型”:
从“洋葱模型”示例图中我们可以很清晰的看到 一个请求从外到里一层一层的经过中间件,响应时从里到外一层一层的经过中间件。上述代码使用 deno run --allow-net oak/oak-middlewares-demo.ts
成功运行后,我们打开浏览器,然后访问 http://localhost:8000/ URL 地址,之后在命令行会输出以下结果:
GET http://localhost:8000/ - 0ms
GET http://localhost:8000/favicon.ico - 0ms
这里我们只是简单介绍了 Oak,如果对 Oak 如何开发 REST API 感兴趣的话,可以阅读阿宝哥 了不起的 Deno 实战教程 这篇文章。
七、阿宝哥有话说
7.1 Deno 如何使用第三方模块
截止目前(2020-06-30)为止,Deno 支持的第三方库的数目为 707。
(图片来源:https://deno.land/x)
下面我们以大家熟悉的 lodash
为例,来看一下如何使用第三方模块。首先在搜索框中输入 lodash
,对应的搜索结果如下:
(图片来源:https://deno.land/x)
接着选择你想要安装的模块,这里阿宝哥选择 deno_lodash
这个模块,点击该列表项会进入该模块的详情页。然后你就可以根据模块的说明文档来安装使用模块了,以下是文档中该模块的使用示例:
import _ from "https://deno.land/x/deno_lodash/mod.ts";
console.log(_.defaults({ 'a': 1}, {'a': 3, 'b': 2}));
// → {'a': 1, 'b': 2}
console.log(_.partition([1, 2, 3, 4], (n:number) => n % 2));
// → [[1, 3], [2, 4]]
7.2 Deno 如何统一管理项目依赖
在 Deno 项目中可以使用一个 deps.ts
文件来统一管理所有依赖,别的地方直接从 deps.ts
统一引入,例如 oak 项目的 deps.ts
如下所示:
// https://github.com/oakserver/oak/blob/main/deps.ts
export {copyBytes, equal} from "https://deno.land/std@0.59.0/bytes/mod.ts";
export {Sha1} from "https://deno.land/std@0.59.0/hash/sha1.ts";
export {HmacSha256} from "https://deno.land/std@0.59.0/hash/sha256.ts";
// 已省略部分代码
// 3rd party dependencies
export {
contentType,
extension,
lookup,
} from "https://deno.land/x/media_types@v2.3.8/mod.ts";
export {
compile,
parse as pathParse,
pathToRegexp,
} from "https://raw.githubusercontent.com/pillarjs/path-to-regexp/v6.1.0/src/index.ts";
那么如何查找这些模块呢?除了在前面提到的 Deno 官网 第三方模块 页面查找之外,你也可以在 pika 中寻找指定的模块,如果搜索出来的结果能直接使用,不会报错,表明该模块可以正常在 deno 中使用。
7.3 Deno 如何使用 npm 包
在 Deno 中如果要使用 npm 包,可以利用 Deno 标准库中提供的 createRequire
方法,具体使用方式如下:
import {createRequire} from "https://deno.land/std/node/module.ts";
const require = createRequire(import.meta.url);
// Loads native module polyfill.
const path = require("path");
// Loads extensionless module.
const cjsModule = require("./my_mod");
// Visits node_modules.
const leftPad = require("left-pad");
除此之外,Deno 还可以借助 jspm.io 的能力来调用 npm 的模块,感兴趣的小伙伴可以阅读 Deno 借助 jspm 使用 npm 这篇文章。
7.4 如何用一个代码库来维护同时支持 Deno 和 npm 的模块
尽管将模块移植到 Deno 非常容易,但是你要维护两个代码库却很麻烦。所以如果你想只使用一个代码库来维护同时支持 Deno 和 npm 的模块,这时你可以考虑使用 denoify 这个工具。
denoify 是一个构建工具,该构建工具将旨在以 node 或 web 为目标的 TypeScript 代码库作为输入,并将源文件的修改版本作为 Deno 模块部署。
(图片来源:https://github.com/garronej/d…)
以下是使用 denoify 开发的模块:
- EVT
- run-exclusive
感兴趣的小伙伴,可以自行了解一下上述的模块。另外,如果你也想要在项目中使用 denoify,那么可以参考这个仓库,该仓库会一步步教你如何在项目中配置 denoify。
八、总结
本文从七个方面入手,介绍了 Deno 入门相关的基础知识,希望对 Deno 初学者能有一些帮助,当然有写得不好的地方欢迎小伙伴们指出。Deno 现在还在高速地发展,标准库还未正式发布稳定的版本,写作本文时,最新版本是 0.59.0。相比成熟的 Node.js 社区,Deno 社区还有很长一段路要走,感兴趣的小伙伴们可以持续关注它。
阿宝哥是在 Deno 1.0.0 发布后才正式学习 Deno,目前也还在不断学习中。对阿宝哥个人来说,近期比较感兴趣的是 Deno Web API,因此近期也花了蛮多时间学习 Deno Web API 的实现。在学习过程中,也遇到挺多问题的,这里要感谢 justjavac(迷渡大大) 的耐心解答。
能看到这里的小伙伴都是“真爱”,最后感谢大家阅读本教程,欢迎对 Deno 感兴趣的小伙伴跟阿宝哥一起交流和学习 Deno。
九、参考资源
- Deno 中文手册
- Deno 如何使用 npm 包?
- 如何评价 deno 计划把一些内部模块从 ts 改回 js?
- Github – oak
- the-deno-handbook
- deno-first-approach
- the-deno-handbook
- write-a-small-api-using-deno
十、推荐阅读
- 了不起的 TypeScript 入门教程
- 一文读懂 TypeScript 泛型及应用
- 了不起的 Deno 入门篇
- 了不起的 Deno 实战教程
- 你不知道的 Blob
- 你不知道的 WeakMap