final
final
屬性與structural
屬性相反。它設定 wasm-bindgen
如何產生 JS 匯入以呼叫匯入的函式。特別是,由 final
匯入的函式在匯入後永遠不會變更,而預設匯入(或使用 structural
)的函式會受到執行階段查詢規則的約束,例如遍歷物件的原型鏈。請注意,final
不適用於存取 JS 物件的資料描述器屬性;若要完成此操作,請使用 structural
屬性。
final
屬性旨在純粹與效能相關。理想情況下,它不應有使用者可見的效果,並且 structural
匯入(預設)應該能夠最終透明地切換到 final
。
最終的效能考量是,使用元件模型提案時,wasm-bindgen
需要產生的 JS 函式墊片會比現在少得多。例如,考慮今天這個匯入
# #![allow(unused_variables)] #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 函式墊片是必要的,但它相對來說是自包含的。然而,它以鴨子類型的方式執行 bar
方法,因為它從不驗證 getObject(arg0)
是否為 Foo
類型,以實際呼叫 Foo.prototype.bar
方法。
但是,如果我們改為寫這個
# #![allow(unused_variables)] #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));
}
這裡的差異非常細微,但我們可以了解如何將被呼叫的函式從產生的墊片中提升出來,並且永遠綁定到 Foo.prototype.bar
。然後,它使用 Function.call
方法以 getObject(arg0)
作為接收器來呼叫該函式。
但是等等,即使使用 final
,這裡仍然有一個 JS 函式墊片!這是真的,這只是未來 WebAssembly 提案尚未實現的事實。但是,語義符合未來的元件模型提案,因為被呼叫的方法只確定一次,並且位於原型鏈上,而不是在呼叫函式時在執行階段解析。
與未來提案的互動
如果您想了解我們的 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));
}
... 一旦參考類型提案實施後,我們就不需要其中一些麻煩的函式。這會將我們產生的 JS 墊片轉換成如下所示
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 函式墊片來呼叫匯入的函式。此外,未來 Wasm 對 ES 模組系統的提案也可能意味著我們甚至不需要這裡的 export const ...
。
還值得指出的是,在實作所有這些 Wasm 提案之後,匯入 bar
函式(又名 structural
)的預設方式會產生一個看起來像這樣的 JS 函式墊片
export function __wbg_bar_a81456386e6b526f(varg1) {
return this.bar(varg1);
}
其中此匯入仍然會受到執行時期原型鏈查找等影響。