在 WebAssembly 初学者指南的第四部分,我们将深入探讨 WebAssembly 与 JavaScript 之间的合作关系。
本文将教你如何在 JavaScript 中运用 WebAssembly 技术。 此外,我们还将探索 WebAssembly JavaScript API 的功能。
WebAssembly 是一种开放的二进制格式标准,它使开发者能够在浏览器上以接近原生的性能运行应用程序。 如果您是初次接触此概念,我们建议您先阅读本指南的前面部分。
让我们开始吧。
WebAssembly 与 JavaScript 的协同使用
在我们的 WebAssembly 系列教程的第一部分中,我们阐述了 WASM 的工作原理。 若要为您的 Web 应用程序编写高效的代码,您必须熟练掌握 WASM API 及在 JavaScript 中的函数调用。 我们还讨论了 JavaScript 框架是如何利用 WASM 来创建高性能应用的。
然而,您目前还不能像加载 ES6 模块那样直接使用 <script type=”module”>
加载 WASM 模块。 这正是 JavaScript 的用武之地。它可以帮助在浏览器中加载和编译 WASM。 具体步骤如下:
- 将 .wasm 字节码加载到 ArrayBuffer 或类型数组中。
- 使用 WebAssembly.Module 编译字节码。
- 现在,通过导入来实例化 WebAssembly.Module,从而获得可调用的导出项。
因此,您需要从预编译的 WASM 模块开始入手。 在这里,您有很多选择。 您可以使用 Rust、C/C++、AssemblyScript,甚至 TinyGo (Go) 来编写代码,然后将其转化为 .wasm 模块。
从技术角度来看,WebAssembly 是多种编程语言的编译目标。 这意味着您需要先使用您选择的语言编写代码,然后在应用程序(无论是 Web 应用还是非 Web 应用)中使用生成的二进制代码。 此外,如果您计划在服务器端使用 WASM,则需要使用 WASI 与系统进行交互。
由于 WebAssembly 使用可扩展数组来实现线性内存管理,JavaScript 和 WASM 都可以同步访问它,这使得您能够开发出功能丰富且运行迅速的应用程序。
WebAssembly 和 JavaScript 的实际应用示例
让我们通过一些实际例子,来了解如何将 WASM 与 JavaScript 结合使用。
如前所述,您需要一个预编译的 WASM 模块。 在此示例中,我们将使用 Emscripten (C/C++) 工具链。 由于 WASM 提供了一种高性能的二进制格式,我们可以将生成的代码与 JavaScript 或其他语言一起运行。
工具设置
因为我们使用的是 Emscripten,所以首先需要安装 emsdk 工具。 它允许您将 C/C++ 代码编译为 .wasm 代码。
只需在您的终端中执行以下命令。 如果您尚未安装 GIT,请参考我们的开源 101 指南:版本控制系统和 Git 安装指南进行安装。
git clone https://github.com/emscripten-core/emsdk.git cd emsdk
# 输出 [email protected]:~/Projects/WASM2$ git clone https://github.com/emscripten-core/emsdk.git Cloning into 'emsdk'... remote: Enumerating objects: 3566, done. remote: Counting objects: 100% (62/62), done. remote: Compressing objects: 100% (49/49), done. remote: Total 3566 (delta 31), reused 38 (delta 13), pack-reused 3504 Receiving objects: 100% (3566/3566), 2.09 MiB | 2.24 MiB/s, done. Resolving deltas: 100% (2334/2334), done. [email protected]:~/Projects/WASM2$ cd emsdk [email protected]:~/Projects/WASM2/emsdk$
进入 emsdk 文件夹后,我们执行另一个命令来获取最新的可用 Emscripten 构建版本。
为此,您需要运行以下命令。
./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh
# 输出 [email protected]:~/Projects/WASM2/emsdk$ ./emsdk install latest Resolving SDK alias 'latest' to '3.1.31' Resolving SDK version '3.1.31' to 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit' Installing SDK 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'.. Installing tool 'node-14.18.2-64bit'.. Downloading: /home/nitt/Projects/WASM2/emsdk/zips/node-v14.18.2-linux-x64.tar.xz from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v14.18.2-linux-x64.tar.xz, 21848416 Bytes Unpacking '/home/nitt/Projects/WASM2/emsdk/zips/node-v14.18.2-linux-x64.tar.xz' to '/home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit' Done installing tool 'node-14.18.2-64bit'. Installing tool 'releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'.. Downloading: /home/nitt/Projects/WASM2/emsdk/zips/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-wasm-binaries.tbz2 from https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a/wasm-binaries.tbz2, 349224945 Bytes Unpacking '/home/nitt/Projects/WASM2/emsdk/zips/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-wasm-binaries.tbz2' to '/home/nitt/Projects/WASM2/emsdk/upstream' Done installing tool 'releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'. Done installing SDK 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'. [email protected]:~/Projects/WASM2/emsdk$ ./emsdk activate latest Resolving SDK alias 'latest' to '3.1.31' Resolving SDK version '3.1.31' to 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit' Setting the following tools as active: node-14.18.2-64bit releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit Next steps: - To conveniently access emsdk tools from the command line, consider adding the following directories to your PATH: /home/nitt/Projects/WASM2/emsdk /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin /home/nitt/Projects/WASM2/emsdk/upstream/emscripten - This can be done for the current shell by running: source "/home/nitt/Projects/WASM2/emsdk/emsdk_env.sh" - Configure emsdk in your shell startup scripts by running: echo 'source "/home/nitt/Projects/WASM2/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile
最后一条命令,”source ./emsdk_env.sh”,用于确保设置 emcc Emscripten 编译器工具的路径,以便您可以利用它来编译代码。
# 输出 [email protected]:~/Projects/WASM2/emsdk$ source ./emsdk_env.sh Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1) Adding directories to PATH: PATH += /home/nitt/Projects/WASM2/emsdk PATH += /home/nitt/Projects/WASM2/emsdk/upstream/emscripten PATH += /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin Setting environment variables: PATH = /home/nitt/Projects/WASM2/emsdk:/home/nitt/Projects/WASM2/emsdk/upstream/emscripten:/home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin EMSDK = /home/nitt/Projects/WASM2/emsdk EMSDK_NODE = /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin/node [email protected]:~/Projects/WASM2/emsdk$
现在,我们需要通过运行以下命令来生成 wasm 代码。
emcc hello-techblik.com.c -o hello-techblik.com.js
# 输出 [email protected]:~/Projects/WASM2$ emcc hello-techblik.com.c -o hello-techblik.com.js shared:INFO: (Emscripten: Running sanity checks) cache:INFO: generating system asset: symbol_lists/1c683af19e290d0b5ca7a8747d74a76f63dcb362.txt... (this will be cached in "/home/nitt/Projects/WASM2/emsdk/upstream/emscripten/cache/symbol_lists/1c683af19e290d0b5ca7a8747d74a76f63dcb362.txt" for subsequent builds) cache:INFO: - ok [email protected]:~/Projects/WASM2$ dir emsdk hello-techblik.com.c hello-techblik.com.js hello-techblik.com.wasm [email protected]:~/Projects/WASM2$
正如您所看到的,您将获得 “hello-techblik.com.js” 和 hello-techblik.com.wasm 的输出文件。 您可以通过在项目目录中运行 dir 命令来查看这些文件。
这两个文件都是必不可少的。 hello-techblik.com.wasm 文件包含了编译后的代码。 另一方面,hello-techblik.com.js 文件则提供了运行该 WASM 代码所必需的 JavaScript 代码。 由于 Emscripten 支持 Web 和 Node.js 执行环境,我们可以使用 Node 来进行测试。
node hello-techblik.com.js
# 输出 [email protected]p:~/Projects/WASM2$ node hello-techblik.com.js Hello, techblik.com! [email protected]:~/Projects/WASM2$
如果您希望在 Web 环境中运行它,可以使用 Emscripten 生成 HTML 文件。 为此,请运行以下命令。
emcc hello-techblik.com.c -o hello-techblik.com.html
# 输出 [email protected]:~/Projects/WASM2$ emcc hello-techblik.com.c -o hello-techblik.com.html [email protected]:~/Projects/WASM2$
若要运行 HTML 文件,可以使用 Python 3 HTTPServer,只需运行以下命令即可。
python3 -m http.server 8000
现在,在浏览器中访问 http://localhost:8000/hello-techblik.com.html 查看输出。
注意:大多数系统中都预装了 Python。 如果没有,您可以在尝试运行 Python3 服务器之前轻松安装它。
使用 JavaScript API 与 WASM 协同工作
本节将深入研究 JavaScript WASM API。 通过它,我们将学习如何加载 WASM 代码并执行它。 但首先,让我们看下面的代码。
fetch('hello-techblik.com.wasm').then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes)) .then(result => alert(result.instance.exports.answer()))
上述代码使用了以下 JavaScript API:
- fetch() 浏览器 API
- WebAssembly.instantiate
除此之外,还有其他一些值得关注的 API,包括:
- WebAssembly.compile
- WebAssembly.Instance
- WebAssembly.instantiate
- WebAssembly.instantiateStreaming
fetch() 浏览器 API
fetch() API 用于加载 .wasm 网络资源。 如果您尝试在本地加载它,则需要禁用跨域资源共享 (CORS) 以加载网络资源。 或者,您可以使用节点服务器为您完成此操作。 要安装和运行节点服务器,请执行以下命令。
sudo apt install npm
接下来,运行以下命令来启动服务器。
npx http-server -o
# 输出 http-server version: 14.1.1 http-server settings: CORS: disabled Cache: 3600 seconds Connection Timeout: 120 seconds Directory Listings: visible AutoIndex: visible Serve GZIP Files: false Serve Brotli Files: false Default File Extension: none Available on: http://127.0.0.1:8080 http://192.168.0.107:8080 Hit CTRL-C to stop the server Open: http://127.0.0.1:8080 [2023-01-28T19:22:21.137Z] "GET /" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70" (node:37919) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated (Use `node --trace-deprecation ...` to show where the warning was created) [2023-01-28T19:22:21.369Z] "GET /favicon.ico" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70" [2023-01-28T19:22:21.369Z] "GET /favicon.ico" Error (404): "Not found"
它将启动 Web 浏览器,您可以在其中查看项目中的所有文件。
现在,打开 hello-techblik.html 并启用 Web 开发工具。 在那里,打开控制台并输入以下命令。
fetch(“hello-techblik.com.wasm”);
它将返回以下 Promise。
# 输出 Promise {} [[Prototype]]: Promise [[PromiseState]]: "fulfilled" [[PromiseResult]]: Response body: (...) bodyUsed: false headers: Headers {} ok: true redirected: false status: 200 statusText: "OK" type: "basic" url: "http://127.0.0.1:8080/hello-techblik.com.wasm" [[Prototype]]: Response
您还可以编写以下脚本并通过 HTML 运行它。
要在服务器上运行 wasm 模块,需要在 Node.js 中使用以下代码。
const fs = require('fs'); const run = async() => { const buffer = fs.readFileSync("./hello-techblik.com.wasm"); const result = await WebAssembly.instantiate(buffer); console.log(result.instance.exports.answer()); }; run();
我们建议查阅 WebAssembly JavaScript API 文档以了解更多信息。
JavaScript 与 WASM 的比较
为了更好地理解 WASM 和 JavaScript 之间的协作关系,我们需要对它们进行比较。 WASM 的核心优势是速度更快,并且具有用于目标编译的二进制格式,而 JavaScript 则是一种高级语言。 WASM 的二进制代码使其学习难度较大,但有一些方法可以高效地使用 WASM。
WASM 和 JavaScript 之间的主要区别包括:
- WASM 是一种编译型语言,而 JS 是一种解释型语言。 浏览器需要在运行时下载并解析 JavaScript,而 WASM 代码由于是预编译的,因此可以立即执行。
- WebAssembly 是一种底层语言。 相比之下,JavaScript 是一种高级语言。 作为一种高级语言,JS 更易于使用。 然而,WASM 由于是低级别的,因此执行速度比 JavaScript 更快。
- 最后,JavaScript 得益于其庞大的社区。 因此,如果您正在寻求更佳的开发体验,那么 JS 是一个明显的选择。 另一方面,WebAssembly 相对较新,因此资源较少。
作为开发人员,您不必为选择其中一个而纠结。 这是因为 JS 和 WASM 是协同工作的,而不是相互竞争的关系。
因此,如果您正在开发高性能的应用程序,您可能希望使用 WebAssembly 仅对那些需要高性能的部分进行编码。 JavaScript API 可以帮助您直接从 JavaScript 代码中获取和使用 WASM 模块。
总结
总而言之,WebAssembly 是 JavaScript 的绝佳伴侣。 它鼓励开发者在 Web 和非 Web 环境中创建高性能的应用程序。 此外,它并不试图取代 JavaScript。
然而,WASM 是否会发展成一个完整的工具包并最终取代 JavaScript 呢? 考虑到 WebAssembly 的设计目标,这似乎不太可能。 但是,关于 WebAssembly 在未来取代 JavaScript 的设想并非完全不可理喻。
接下来,请阅读关于构建现代应用程序的最佳 JavaScript (JS) UI 库的文章。