摘要
文章从模块化编程的概念和优势出发,阐述 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阶段。
