摘要
文章从模块化编程的概念和优势出发,阐述 JavaScript 和 asm.js 的模块化与动态链接机制,详细分析 WebAssembly 的模块化、动态链接关键设计和实现,探讨 WebAssembly 在不同场景下的动态链接示例,最后介绍其动态链接发展趋势,包括相关提案的原理和目标。
一、模块化编程概念与优势
模块化编程是一种软件设计模式,将软件分解为独立、可替换、功能预定的模块,各模块通过接口组合成最终程序。
- 流行语言如 JavaScript、Python、Rust、Java 和 C ++ 20 等都有模块管理。
- 其具有易设计、易实现、易测试、易维护、可重用以及低耦合、高隔离性等特点。例如,将复杂问题分解为简单问题便于理解系统结构(易设计);适合团队开发,成员只需关注小任务(易实现);模块可独立测试(易测试);修改或扩展系统功能只需针对特定模块(易维护);模块代码可复用(可重用);模块有各自作用域避免变量污染等(低耦合、高隔离性)。
二、JavaScript 和 asm.js 的模块化与动态链接
(一)JavaScript 模块与动态链接
JavaScript 有多种模块化规范,这里以 CommonJS 为例。
- 在 CommonJS 中,单个 JavaScript 文件可作为模块,默认定义仅内部可见,通过
module.exports
对外暴露接口和对象,require()
用于获取其他模块导出对象。例如在计算矩形面积的示例中,square.js
定义Square
类,calculator.js
通过require(square.js)
加载square.js
模块并导入Square
类实现代码复用。 - 接着分析了 NodeJS 中 CommonJS 模块的加载和动态链接过程。
wrap
是模块包装器函数,wrapper
是模块包装模板。在 JavaScript 文件加载时,NodeJS Loader 会用wrap
将源代码包装成匿名函数表达式对象,限制顶级变量作用域在模块内,函数表达式参数module
、exports
用于导出值,require
用于导入外部模块。- 当 JavaScript 通过
require(path)
加载模块文件时,实际执行Module.prototype.require
函数,进而调用Module._load
执行模块加载和链接。Module._load
为提升效率定义了Module._cache
用于缓存已加载模块,若模块已加载则直接返回,否则创建Module
实例放入缓存并调用load
函数执行加载和链接。 Module.prototype.load
函数根据文件扩展名获取处理函数,加载 JavaScript 模块时调用Module._extensions['.js']
处理函数完成编译和执行逻辑。Module._extensions['.js']
处理函数先通过fs
加载文件内容,再调用Module.prototype._compile
函数编译模块文件内容。Module.prototype._compile
函数在模块上下文中执行匿名函数对象并传递正确参数,从而完成 CommonJS 模块的动态加载和链接过程。
(二)asm.js 模块与动态链接
asm.js 是 WebAssembly 的前身,是可编译期优化的 JavaScript 严格子集。
- 一个标准的 asm.js 模块结构,其内部被分为变量定义、函数定义和函数导出三部分。模块通过
“use asm”
标记声明,最多接受三个可选参数:stdlib
提供对 JavaScript 标准库有限子集的访问;foreign
提供对自定义外部 JavaScript 函数的访问;heap
提供ArrayBuffer
作为 asm.js 堆。 - asm.js 模块参数使得模块可调用外部 JavaScript 并共享
ArrayBuffer
堆缓冲区,从模块返回的导出对象允许外部 JavaScript 调用 asm.js,这个交互和绑定过程称为 asm.js 模块链接,其模块化和链接机制基本沿袭 JavaScript 并被 WebAssembly 继承发展。
三、WebAssembly 模块及动态链接
WebAssembly 在 asm.js 基础上扩展了模块和链接机制,定义import
和export
段。
(一)WebAssembly exports -> JavaScript imports
以shared - module.wasm
为例,它导出 WebAssembly 核心类型对象。JavaScript 宿主在运行期动态加载shared - module.wasm
文件,创建模块实例,为 WebAssembly 执行环境中的exports
对象在 JavaScript 环境创建对应实例,之后就可按 JavaScript 访问方式访问和调用这些对象。
(二)JavaScript exports -> WebAssembly imports
对于user - module.wasm
,其依赖宿主环境提供的 WebAssembly 核心类型对象。JavaScript 宿主需提供JSModule
对象,WebAssembly 虚拟机创建对应的实例对象并绑定,之后 WebAssembly 执行环境可按原生对象访问方式访问JSModule
对象。
(三)WebAssembly exports -> WebAssembly imports
现有 WebAssembly 引擎中模块间链接实现未标准化,在 JavaScript 环境中可通过前两种方式组合间接实现。例如,JavaScript 运行环境先加载并实例化shared - module
,将其导出变量绑定到JSModule
对象,这些变量作为不可变绑定提供给其他 WebAssembly 模块,在user - module.wasm
模块加载时,将JSModule
中对应实例绑定导入到user - module
中完成动态链接。
四、WebAssembly 动态链接发展趋势
(一)WebAssembly/ES Module Integration
目前通过 W3C 标准 JavaScript API 实例化 WebAssembly 模块存在不友好之处,需要手动操作多个步骤。ECMAScript Module Integration
提案尝试添加声明式 API 来隐藏这些过程,使 WebAssembly 模块可直接使用 JavaScript 模块的导出对象,还意图实现 WebAssembly 模块与 JavaScript 模块的融合。
(二)Module Linking Proposal
Module Linking
提案希望建立可移植、独立于宿主和语言的可组合 WebAssembly 模块生态系统。它扩展 WebAssembly 模块规范,在二进制格式中添加新的Section
和索引空间,避免依赖运行时加载程序,让 WebAssembly 运行时完成所有工作,但该提案目前处于“inactive”状态,相关工作转到“Component Model”提案。
(三)Component Model Proposal
Component Model
提案希望“自上而下”制定 WebAssembly 下一代标准,核心内容覆盖Module - Link
提案关键内容,包括定义可独立编译构建的二进制格式组件、支持多种特性、保持 WebAssembly 独特价值等多项目标,还采用增加间接中间层“Module Linking Layer”的方式来设计,目前处于非常初期的Feature Proposal
阶段。