最终
final
属性与 structural
属性相反。它配置 wasm-bindgen
如何生成 JS 导入以调用导入的函数。值得注意的是,通过 final
导入的函数在导入后永远不会更改,而默认导入(或使用 structural
)的函数受运行时查找规则的约束,例如遍历对象的原型链。请注意,final
不适合访问 JS 对象的数据描述符属性;要完成此操作,请使用 structural
属性。
final
属性旨在纯粹与性能相关。理想情况下,它没有用户可见的效果,并且 structural
导入(默认)最终应该能够透明地切换到 final
。
最终的性能方面是,使用 组件模型提案,wasm-bindgen
将需要生成比现在少得多的 JS 函数 shim 来导入。例如,考虑一下今天的这个导入
#![allow(unused)] fn main() { #[wasm_bindgen] extern "C" { type Foo; #[wasm_bindgen(method)] fn bar(this: &Foo, argument: &str) -> JsValue; } }
没有 final
属性,生成的 JS 如下所示
// without `final`
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
let varg1 = getStringFromWasm(arg1, arg2);
return addHeapObject(getObject(arg0).bar(varg1));
}
我们在这里可以看到,需要这个 JS 函数 shim,但它都是相对独立的。然而,它以一种鸭子类型的方式执行 bar
方法,因为它永远不会验证 getObject(arg0)
的类型是否为 Foo
以实际调用 Foo.prototype.bar
方法。
如果我们改为编写此代码
#![allow(unused)] fn main() { #[wasm_bindgen] extern "C" { type Foo; #[wasm_bindgen(method, final)] // note the change here fn bar(this: &Foo, argument: &str) -> JsValue; } }
它会生成这个 JS 粘合代码(大致)
const __wbg_bar_target = Foo.prototype.bar;
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
let varg1 = getStringFromWasm(arg1, arg2);
return addHeapObject(__wbg_bar_target.call(getObject(arg0), varg1));
}
这里的差异非常微妙,但我们可以看到如何将调用的函数从生成的 shim 中提升出来,并绑定到始终为 Foo.prototype.bar
。然后,它使用 Function.call
方法以 getObject(arg0)
作为接收器来调用该函数。
但是等等,即使使用了 final
,这里仍然有一个 JS 函数 shim!这是真的,这仅仅是未来的 WebAssembly 提案尚未实施的事实。但是,语义与未来的 组件模型提案 相匹配,因为要调用的方法只确定一次,并且它位于原型链上,而不是在调用函数时在运行时解析。
与未来提案的交互
如果您想知道我们的 JS 函数 shim 如何完全消除,让我们看一下生成的绑定。我们从这里开始
const __wbg_bar_target = Foo.prototype.bar;
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
let varg1 = getStringFromWasm(arg1, arg2);
return addHeapObject(__wbg_bar_target.call(getObject(arg0), varg1));
}
... 一旦 引用类型提案 实现,我们将不需要这些讨厌的函数。这将把我们生成的 JS shim 转换为如下所示
const __wbg_bar_target = Foo.prototype.bar;
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
let varg1 = getStringFromWasm(arg1, arg2);
return __wbg_bar_target.call(arg0, varg1);
}
越来越好!接下来我们需要组件模型提案。请注意,该提案目前正在进行一些更改,因此很难链接到参考文档,但足以说明它将至少为我们提供两个不同的功能。
首先,组件模型承诺提供“参数转换”的概念。这里的 arg1
和 arg2
值实际上是指向 utf-8 编码字符串的指针和长度,使用组件模型,我们将能够注释此导入应该采用这两个参数并将它们转换为 JS 字符串(也就是说,主机应该这样做,WebAssembly 引擎)。使用该功能,我们可以进一步将其缩小到
const __wbg_bar_target = Foo.prototype.bar;
export function __wbg_bar_a81456386e6b526f(arg0, varg1) {
return __wbg_bar_target.call(arg0, varg1);
}
最后,组件模型提案的第二个承诺是,我们可以标记一个函数调用,以表明第一个参数是该函数调用的 this
绑定。目前,所有被调用的导入函数的 this
值都是 undefined
,而这个标志(通过组件模型配置)将表明这里的第一个参数实际上是 this
。
考虑到这一点,我们可以进一步将其转换为:
export const __wbg_bar_a81456386e6b526f = Foo.prototype.bar;
瞧!通过引用类型和组件模型,我们现在完全不需要 JS 函数垫片来调用导入的函数。此外,未来针对 ES 模块系统的 Wasm 提案可能也意味着我们甚至不需要这里的 export const ...
了。
值得指出的是,在所有这些 Wasm 提案实现后,导入 bar
函数(也称为 structural
)的默认方式将生成一个类似于以下的 JS 函数垫片:
export function __wbg_bar_a81456386e6b526f(varg1) {
return this.bar(varg1);
}
而这个导入仍然会受到运行时原型链查找等的影响。