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);
}

越來越好了!接下來我們需要元件模型提案。請注意,該提案目前正在進行一些變更,因此很難連結到參考文件,但可以說它至少會為我們提供兩個不同的功能。

首先,元件模型承諾提供「參數轉換」的概念。這裡的 arg1arg2 值實際上是指向 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);
}

其中此匯入仍然會受到執行時期原型鏈查找等影響。