final

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,但它都相對獨立。然而,它確實以 duck-type 的方式執行 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 glue(大約)

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

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

首先,元件模型承諾提供「引數轉換」的概念。此處的 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 函式 shim 來呼叫導入的函式。此外,未來針對 ES 模組系統的 Wasm 提案也可能表示我們甚至不需要此處的 export const ...

還值得指出的是,在實作所有這些 Wasm 提案後,導入 bar 函式(又名 structural)的預設方式將產生一個如下所示的 JS 函式 shim

export function __wbg_bar_a81456386e6b526f(varg1) {
    return this.bar(varg1);
}

此導入仍然受到執行時原型鏈查詢的約束。