- 开发工具
【免费下载链接】wasm-bindgen
Facilitating high-level interactions between Wasm modules and JavaScript
本指南聚焦 wasm-bindgen 的#[wasm_bindgen(method, getter)]与#[wasm_bindgen(method, setter)]属性,讲解如何在 Rust 中声明 JavaScript 类实例属性的读取与写入访问器。你将掌握默认命名规则(getter 同名、setter 的set_前缀)、通过属性参数显式指定属性名、structural与默认"原型链缓存"两种访问模式的差异,以及"[Symbol.<name>]"形式的 well-known symbol 绑定技巧。
一、属性概述:把 JavaScript 属性当作 Rust 方法调用
getter和setter是两个只能与method组合使用的属性(method的完整说明见 method 属性)。它们把 Rust 中导入的函数声明标记为对 JavaScript 对象"属性"的访问器:
- 带
getter的函数,默认访问与Rust 函数同名的 JavaScript 属性; - 带
setter的函数,函数名必须以set_开头,实际访问的属性名是set_之后的后缀部分。
这一规则由宏解析器在语法层面直接落地:在 crates/macro-support/src/parser.rs 中,getter与setter被解析为Getter(Span, Option<String>)与Setter(Span, Option<String>)两个可带可选字符串参数的属性;随后在 同文件 L3352-L3356 处被转换为ast::OperationKind::Getter/Setter操作类别。
二、默认命名规则与最小可用示例
假设 JavaScript 端有一个对white_russians属性定义 getter/setter 的类:
class TheDude { get white_russians() { ... } set white_russians(val) { ... } }对应的 Rust 导入写法如下:
#[wasm_bindgen] extern "C" { type TheDude; #[wasm_bindgen(method, getter)] fn white_russians(this: &TheDude) -> u32; #[wasm_bindgen(method, setter)] fn set_white_russians(this: &TheDude, val: u32); }这里导入了TheDude类型,并定义了访问每个实例white_russians属性的能力:
- 第一个函数是 getter,在 Rust 中通过
the_dude.white_russians()调用(方法式调用); - 第二个函数是 setter,通过
the_dude.set_white_russians(2)写入值。
注意:由于两个函数都标记了method,因此必须带有this: &TheDude作为第一个参数(详见 method 属性)。
三、显式指定属性名:getter = name/setter = name
getter和setter都可以携带一个字符串参数,用来显式声明实际访问的属性名。一旦显式指定,对 Rust 函数名就不再有任何约束(getter 不再要求同名,setter 不再要求set_前缀):
#[wasm_bindgen] extern "C" { type TheDude; #[wasm_bindgen(method, getter = white_russians)] fn my_custom_getter_name(this: &TheDude) -> u32; #[wasm_bindgen(method, setter = white_russians)] fn my_custom_setter_name(this: &TheDude, val: u32); }这段代码与上一节完全等价:两个函数虽然在 Rust 中叫my_custom_getter_name/my_custom_setter_name,但访问的都是white_russians属性。这一显式形式也方便同时使用js_name进一步控制 Rust 侧标识符,二者可以自由组合(属性名与js_name的交互规则在测试中有系统验证,见下文)。
四、两种访问模式:默认原型缓存 vsstructural
务必注意:getter/setter函数在加载时只从构造函数的原型链上查找一次,结果被缓存,之后每次访问都直接调用缓存下来的访问器(accessor)。
默认模式下,Rust 调用the_dude.white_russians()实际生成的 JavaScript 等价于:
// 这是默认情况下 Rust 调用 the_dude.white_russians() 时所执行的逻辑: const white_russians = Object.getOwnPropertyDescriptor( TheDude.prototype, "white_russians" ).get;也就是说,访问器函数指针在模块初始化时就被"钉死"。如果需要在每一次访问时都动态遍历原型链(例如对象被继承、属性被动态覆写,或传入的不是该构造函数的实例),请加上structural属性:
// 这是加上 structural 之后的效果: const white_russians = function(the_dude) { return the_dude.white_russians; };structural的具体语义见 structural 属性:它表示以"结构化、鸭子类型"的方式访问方法或属性,每次访问都沿原型链实时查找。该文档同时指出,自 [RFC 5] 之后structural已是所有导入函数的默认行为,属性本身目前很大程度上被忽略、仅为向后兼容保留;其反面final属性 反而更具功能意义。因此对getter/setter而言,"原型链缓存"对应的是非structural(即final)语义,加不加structural决定了访问器是静态固化还是动态查找。
源码层面的两种生成路径
这两种模式在编译器中对应完全不同的代码生成分支,可以从 crates/cli-support/src/wit/mod.rs 中清晰地看到:
structural模式下,getter 被转译为AuxImport::StructuralGetter(field),setter 被转译为AuxImport::StructuralSetter(field)(静态成员则对应StructuralClassGetter/StructuralClassSetter);- 非
structural模式下,则生成AuxValue::Getter(class, field)/AuxValue::Setter(class, field)(静态成员对应ClassGetter/ClassSetter),走的是"加载时取访问器"的 Value 管线。
随后在 crates/cli-support/src/js/mod.rs 中,StructuralGetter被内联生成receiver + 属性访问的表达式(严格断言只允许 1 个参数),StructuralSetter则生成receiver + 属性访问 = 新值的赋值表达式(严格断言恰好 2 个参数)。这些断言与文档中"getter 带this、setter 带this和val"的签名约定一一对应。
五、非合法标识符与原型链兜底:编译器如何保证正确性
property_accessor是生成属性访问表达式的核心辅助函数,定义在 crates/cli-support/src/js/mod.rs:
- 属性名是合法 JS 标识符时生成点号访问,如
foo.bar; - 属性名不是合法 JS 标识符时自动转为方括号字符串形式,如
foo["kebab-case"]; - 以
[开头、以]结尾的名称被视为计算键表达式,原样拼接(这正是"[Symbol.iterator]"这类 Symbol 键的入口,见下文); - 点分路径(如
"prototype.set.call")会按.拆分后逐段渲染,以保留历史行为。
另外,由于默认模式在加载时要沿原型链查找访问器,而个别浏览器存在"将描述符上移到原型链"的异常行为,编译器内置了一个GetOwnOrInheritedPropertyDescriptor兜底函数,手动遍历原型链定位属性描述符,见 crates/cli-support/src/js/mod.rs。这是默认缓存模式稳定性的底层保障。
六、绑定 well-known symbol 访问器
getter和setter都支持显式的方括号字符串形式"[Symbol.<name>]",用于绑定以 well-known symbol 为键的访问器:
#[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = String)] type JsString; #[wasm_bindgen(method, js_class = "String", getter = "[Symbol.toPrimitive]")] fn to_primitive(this: &JsString) -> String; }这段代码绑定的是String.prototype上的[Symbol.toPrimitive]getter(注意同时使用了js_class = "String"指定构造函数;js_name与js_class的配合规则见 js_name 属性)。
关于该语法有三点限制:
- 只接受精确形式
"[Symbol.<ident>]",[ ... ]内部不支持任意表达式; - 同样的语法也被
js_name支持,用于非 getter/setter 的普通导入(例如js_namespace = SomeClass, js_name = "[Symbol.toPrimitive]"的静态调用,或method, js_class = "String", js_name = "[Symbol.iterator]"的方法绑定); - 从前文可知,
property_accessor正是通过"以[开头、以]结尾即按计算键原样拼接"这一规则来识别并透传 Symbol 键的。
七、测试验证:getter/setter 全组合矩阵
仓库提供了系统性的测试来验证 getter/setter 与js_name、显式属性名之间的全部组合,见 tests/wasm/getters_and_setters.rs 与配套的 tests/wasm/getters_and_setters.js。
Rust 侧定义了大量命名刻意区分的组合,例如:
- 无
js_name、无显式属性名(默认同名 getter、set_前缀 setter); - 仅显式属性名(
getter = new_.../setter = new_...),函数名完全自由; - 仅
js_name(重命名 Rust 侧方法名); js_name+ 显式属性名同时使用;- getter 与 setter 使用相同属性名、不同 Rust 方法名的场景。
JS 侧测试脚本则逐一断言:默认模式下 Rust 方法以函数调用形式出现(rules.no_js_name__...()),而getter/setter声明的属性以直接属性读写的形式出现(rules.new_js_name__... = value * 2),从而端到端验证了编译产物的行为符合文档所述语义。
八、快速参考:关键规则一览
| 规则 | 说明 |
|---|---|
| 组合前提 | 必须与method一起使用,函数需带this: &T参数 |
| getter 默认命名 | 访问与 Rust 函数同名的 JS 属性 |
| setter 默认命名 | Rust 函数名必须以set_开头,访问set_之后的部分 |
| 显式指定 | getter = prop/setter = prop,指定后函数名无任何限制 |
| 默认访问模式 | 加载时沿构造函数原型链查找一次并缓存访问器 |
structural | 每次访问动态遍历原型链(当前已是默认语义,属性主要为向后兼容保留) |
| Symbol 键 | 仅接受精确形式"[Symbol.<ident>]",同语法亦适用于js_name |
| 实例 vs 静态 | 实例访问器作用于实例对象;静态成员由编译器生成ClassGetter/ClassSetter分支 |
综上,getter/setter属性是 wasm-bindgen 在 Rust 侧表达 JavaScript 属性访问的标准方式:默认命名规则让常见场景零配置即可工作,显式属性名与js_name的组合提供了完整的命名自由,structural切换访问模式,而"[Symbol.<name>]"形式打通了 well-known symbol 的绑定通道。理解这三层能力(命名、访问模式、Symbol 键)后,你就能在 Rust 中准确、高效地操作任何 JavaScript 类实例的属性。
- 开发工具
【免费下载链接】wasm-bindgen
Facilitating high-level interactions between Wasm modules and JavaScript
相关推荐
Chat2DB:一个工具连 40+ 种数据库,AI 帮你生成 SQL
Chat2DB:一个工具连 40+ 种数据库,AI 帮你生成 SQL Chat2DB 是一款运行在你自己电脑上的免费数据库客户端与 SQL 工作台。它支持连接
开发工具JavaScript 对象属性访问器详解:Getter 与 Setter 的妙用
JavaScript 对象属性访问器详解:Getter 与 Setter 的妙用 前言 在 JavaScript 中,对象属性分为两种类型:数据属性和访问器属性
文档/教程前端howdoi VS Code 扩展离线安装指南:基于 .vsix 打包文件完成本地部署
howdoi VS Code 扩展离线安装指南:基于 .vsix 打包文件完成本地部署 导读 本文基于当前仓库 extension/vscode pkg/ 目录
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考