如何使用 JavaScript 中的 WASM2025年4月18日 | 阅读 12 分钟 什么是 WebAssembly (Wasm)?Wasm,即 WebAssembly 的缩写,是一种二进制指令格式,用于编译器将 C、Rust 和 C++ 等高级语言转换为浏览器或其他编译目标可以执行的低级机器码。它允许开发者以接近原生的速度在 Web 浏览器中运行用这些语言编写的代码,从而使更多人能够进行 Web 应用开发。与 JavaScript 不同,JavaScript 是解释执行或即时 (JIT) 编译的,Wasm 则是在编译时预先编译成紧凑的二进制格式,可以被现代浏览器的 JavaScript 引擎快速加载和运行。 Wasm 是一种可移植、安全且高效的小型虚拟机堆栈机(virtual stack machine)二进制指令集。其主要目的是提供一个与平台无关、灵活且高性能的运行时环境,可在所有主机环境中使用,例如 Web 浏览器、Node.js 甚至嵌入式设备。因此,对于寻求性能提升的 Web 开发者来说,Wasm 是一项出色的技术,特别是在游戏、视频处理、机器学习和加密等计算密集型任务上。 为何创建 WebAssembly?在浏览器中,JavaScript 曾经是客户端脚本唯一可用的语言。现在 JavaScript 的性能和能力都已非常出色,但它并非为某些计算密集型任务(尤其是需要快速执行的任务)而设计。asm.js 等技术试图通过提供优化 JavaScript 代码的方式来弥合这一差距,但它们也存在局限性。 JavaScript 的开销:由于 JavaScript 是一种动态解释型语言,其运行时也存在性能开销。尽管其运行时行为(如即时 (JIT) 编译和动态类型)在许多情况下非常高效,但最终也会导致效率低下,尤其是在 CPU 密集型任务(如复杂的数学计算或图形渲染)中。其开销限制了其在处理性能关键型应用程序方面的效率。 有限的语言支持:此外,JavaScript 是目前浏览器唯一支持的语言,因此开发者只能使用 C、C++ 和 Rust 等低级语言。这些语言提供的内存和系统资源控制优于其他语言,在需要获得最佳性能时具有优势。 代码可移植性:WebAssembly (Wasm) 解决了这两个问题,它是一种可移植的低级字节码格式,可以在任何能够运行兼容运行时的平台上执行,包括 Web 浏览器和 Node.js。在这些环境中,它可以以接近原生的速度直接运行从 C/C++ 或 Rust 等语言编译而来的二进制文件。 WebAssembly 是如何工作的?WebAssembly (Wasm) 是一种在 Web 环境中运行的代码的编译格式,基于 ECMAScript 语言规范的 Wasm 规范扩展,可实现高性能执行。它速度快,并且可以在任何具有兼容运行时的环境中运行,包括浏览器和 Node.js。 该过程涉及三个关键步骤: 编译:然后,使用 Emscripten (C/C++) 或 wasm_pack (Rust) 等工具,将高级源代码编译为 WebAssembly 字节码。输出是一个 .wasm 文件,与 JavaScript 相比,它非常紧凑,因此在处理性能关键内容时可以快速解析。 实例化:加载后,.wasm 文件进入浏览器或宿主环境。WebAssembly 模块被实例化,并且任何必需的导入(例如,JavaScript 函数和浏览器 API)都会被链接。现在,我们已经进入了 Wasm 模块被创建以运行的环境。 执行:为了调用它们,WebAssembly 函数一旦实例化就可以直接从 JavaScript 调用。这意味着 WebAssembly 可以处理浏览器中的所有任务,如游戏、图像处理和机器学习。 WebAssembly 的关键特性
方法一:使用 Fetch API 和 WebAssembly.instantiate()我们使用 Fetch API 和 WebAssembly.instantiate() 来引用编译后的代码,以便在浏览器中进行提取。 使用 Fetch API 和 WebAssembly.instantiate() 在 JavaScript 中加载和运行 WebAssembly (.wasm) 模块非常简单。我认为这种方法非常适合初学者和小项目,因为它很简单,并且您可以直接输入数据。 Fetch API 是 JavaScript 的一项现代功能,允许您异步发起 HTTP 请求来获取资源(例如 .wasm 文件)。最常见的用途是在不重新加载页面的情况下访问服务器上的资源。 在 JavaScript 中,我们有 WebAssembly API,WebAssembly.instantiate() 是其中一个 API,它接受 .wasm 二进制代码(作为 ArrayBuffer)和一个可选的导入对象。它会编译、实例化并返回一个具有导出函数的实例,我们可以直接从 JavaScript 调用这些函数。 C/C++ 代码 (Wasm 模块) 首先,假设您有以下 C/C++ 代码,您将将其编译为 WebAssembly (module.wasm) 使用 Emscripten 等工具编译此代码 JavaScript 代码 说明WebAssembly 二进制文件直接包含在 JavaScript 代码中,作为 Base64 字符串。这意味着我们不需要 .wasm 文件。base64ToArrayBuffer() 函数接收 Base64 字符串,并将其作为 ArrayBuffer 返回,然后该 ArrayBuffer 可用于创建此 WebAssembly 模块。loadWasm() 接收 Base64 编码的二进制 Wasm,并通过转换并存储在 wasmInstance 中来调用 wasmInstance。 有四个 WebAssembly 函数(add、subtract、multiply 和 divide),当单击相应的按钮时,它们会从 JavaScript 调用。 用户可以输入数字并使用 HTML UI 进行算术运算。结果显示在结果 div 中。 方法二:使用 WebAssembly.instantiateStreaming()WebAssembly.instantiateStreaming 是一项现代 API,旨在更轻松地加载和编译 WebAssembly (Wasm) 模块。流式实例化与 WebAssembly.instantiate() 不同,后者需要先获取 WebAssembly 二进制文件才能编译;而使用 instantiateStreaming(),WebAssembly 模块将在下载过程中进行编译。 这在加载大型模块时特别有用,因为模块一旦下载并编译了足够的部分就可以开始执行,从而节省了整体加载时间。 使用 WebAssembly.instantiateStreaming() 的步骤获取 WebAssembly 二进制文件:因此,第一步是通过 fetch() 请求从服务器获取 WebAssembly 模块。它从给定的 URL 下载二进制文件(通常以 .wasm 结尾)。 流式传输 WebAssembly 二进制文件:使用 instantiateStreaming(),.wasm 文件可以分块处理,而不是等待其完全下载。数据会流式传输并实时编译,从而加快了工作速度。其中一个好处是 WebAssembly 模块比传统的下载后编译方法更快地准备好执行。 实例化模块:模块流式传输和编译后,它将被实例化为一个 JavaScript 对象,用于交互。根据需要,您可以从 JavaScript 调用您实例化的 WebAssembly 模块中的导出函数。 WebAssembly.instantiateStreaming() 结合了获取和编译步骤,提供了一种高效快速使用 WebAssembly 模块的方法,特别适用于大型文件或需要快速初始化的应用程序。为了使此工作正常进行,.wasm 文件必须以正确的 MIME 类型(例如 application/wasm)提供。由于现代浏览器支持此方法,因此因性能提升而广泛优于早期方法。 程序 说明 instantiateStreaming():此函数在文件从服务器传入时实时获取并编译 .wasm 模块,与一次性下载所有内容相比,增强了加载过程。 Wasm 模块:我们编写 JavaScript 代码,该代码在获取、实例化并使用 WebAssembly 二进制文件后,最终会调用 JavaScript 代码中的导出函数(如 add 和 subtract)。 方法三:分别使用 WebAssembly.compile() 和 WebAssembly.instantiate()编译 WebAssembly 模块:第一步是使用 WebAssembly.compile() 方法将 .wasm 二进制文件编译成内存中的 WebAssembly.Module 对象。此方法接受二进制数据(例如 .wasm 文件的 ArrayBuffer),并将其转换为 WebAssembly 运行时可以理解的模块格式。这个编译好的模块还不能执行。 实例化模块:在编译模块之后,下一步是使用 WebAssembly.instantiate() 方法对其进行实例化。此过程会创建模块的实例,其中包括内存、导入和函数。实例化会将编译好的模块链接到任何必要的导入(如 JavaScript 函数或其他 WebAssembly 模块),并为其执行做好准备。在实例化期间,您还可以处理特定的配置或设置任务,例如初始化内存或定义导入。 通过将编译和实例化步骤分开,这种方法提供了更大的灵活性。例如,您可以编译一次模块并多次实例化它,或者您可以在执行前进行其他设置。这种方法也允许更精确的错误处理和优化策略,尤其是在处理大型或复杂模块时。当您需要控制实例化流程或在使用前预编译模块时,此技术很有用,可确保高效的资源管理和改进的性能。 程序 说明 获取 WebAssembly 二进制文件:该函数首先从服务器检索 WebAssembly 二进制文件,这通常是一个 .wasm 文件。使用 get() API,通过提供的 URL 从(wasmUrl)检索 Wasm 文件作为 ArrayBuffer。 编译 WebAssembly 模块:检索到二进制文件后,将 ArrayBuffer 作为参数传递给 WebAssembly.compile() 方法。该函数将二进制文件编译成内存中的 WebAssembly.Module 对象。但此时,该模块还不能用于执行。 实例化模块:必须使用 WebAssembly.instantiate() 方法实例化编译好的模块。在此阶段会创建一个 WebAssembly 实例,其中包含内存、导入和导出函数。可以通过 instance.exports 对象访问从 Wasm 模块导出的函数。在这里,使用 add 和 subtract 函数执行算术运算。 使用 WebAssembly 函数:在实例化 WebAssembly 模块后,JavaScript 会调用 add 和 subtract 函数。加法和减法的结果显示在 ID 为 output 的 HTML 元素中。 分开使用 WebAssembly.compile() 和 WebAssembly.instantiate() 的好处编译和实例化的分离 这种方法可以更精细地控制这两个阶段。为了最大化资源并最小化重复编译,开发者可以只编译一次 WebAssembly 模块并多次实例化。 更好的错误处理 通过分离编译和实例化,开发者可以更熟练地调试和处理问题,从而在每个阶段都能检测到错误。 资源管理使用此方法可以更有效地管理内存和其他资源。只需编译一次模块,您就可以在应用程序的其他区域使用它,而无需重新加载或重新编译,这提高了资源密集型应用程序的性能。 下一话题JavaScript 图像源 |
我们请求您订阅我们的新闻通讯以获取最新更新。