使用Go開發前端應用(二)

前言

在以前的第一篇文章:juejin.im/post/5e9ee0… 中,大概介紹了怎麼使用Go去編寫代碼,而後將代碼編譯成wasm,以後能夠在js中使用wasm。是一個很是簡單的demo,這篇文章主要會用來說解兩個方面的知識html

  • 簡單介紹wasm
  • js中加載wasm
  • js中訪問wasm中導出的方法
  • wasm中訪問js中的方法

簡單介紹wasm

原本不想介紹,由於網上已經有不少這方面的介紹了,可是爲了你們在看這系列文章的時候,能夠有個快速的瞭解,這裏仍是大概介紹下wasm。前端

特色:node

  • 執行快速,接近機器語言,執行更加快速
  • 安全,沙箱環境
  • 與硬件無關
  • 與語言無關,沒有特定開發語言,可使用其餘高級語言開發,編譯層wasm
  • 平臺無關,能夠在瀏覽器,node等環境運行
  • 體積更小,傳輸效率更高,比js
  • 模塊化
  • 邊接收,邊解碼,驗證,編譯
  • AOT,JIT都支持
  • 可並行化,好比解碼,驗證,編譯能夠並行執行

這裏簡單說一下爲何wasm執行速度快?首先,wasm更加接近機器語言,咱們知道,在計算機中運行最快的就是機器語言,由於機器能夠直接運行,不須要編譯,轉換等各類操做。wasm呢,它是一種二進制格式的文件,體積很是小,至少比js要小很是多,這樣在加載的時候,首先就比js快了對不對?其次,wasm它還能夠一邊接收服務端的返回數據,一邊就開始解碼,這你應該明白對不對?js都是要下載完整個js以後才能開始解釋執行。數組

基礎類型:瀏覽器

  • i32
  • i64
  • f32
  • f64

wasm裏面只有這4種基礎類型,因此你若是須要處理字符串,那就須要轉換爲int類型的數組,而後wasm才能處理。安全

函數支持:bash

跟其餘語言的函數同樣,沒什麼好說的。模塊化

幾個階段:函數

  • 編譯,將二進制解碼爲模塊的內部表現形式,實際實現能夠直接編譯爲機器代碼。
  • 驗證,主要驗證解碼以後的結果是否正確,好比,格式,安全性等。
  • 執行
    • 實例化,定義模塊的導入,初始化導出等
    • 調用,實例化完成以後,就能夠調用導出的方法了

另外還有一點很重要,wasm它沒有本身特定的開發語言,須要你是用別的語言,好比說Go,Rust。這樣,你用Go或者Rust寫好你的代碼以後呢,將代碼編譯成wasm,這樣在js中就能使用了。post

因此,若是你正好會Go或者Rust的話,是否是優點就來了?由於至少目前來看,前端使用wasm的場景仍是很是多的。

js中加載wasm

首先介紹下js中一些關於wasm的API。具體詳細的API能夠參考: developer.mozilla.org/zh-CN/docs/…

WebAssembly 全局對象

這個對象裏面,包括了全部可使用的WebAssembly的功能。若是你的瀏覽器沒有這個對象,只能說明你用的多是遠古時期的瀏覽器。

對吧,你看這個瀏覽器兼容性就知道了,出了很是坑的IE(誰如今還要支持IE的,舉個手?),其餘瀏覽器都是支持的。

WebAssembly.compile()

compile方法用來將wasm的二進制代碼到一個WebAssembly.Module 對象。這個應該很容易理解對吧?由於返回過來的是二進制呀,你若是須要在js中使用的話,你必需要轉換爲js中能夠操做的東西,對吧。這個方法就是幹這個事情的。

WebAssembly.Module

WebAssembly.Module就是經過compile()方法編譯以後,獲得輸出,可是這個輸出若是要使用呢,如今仍是不行的,還須要實例化,這個跟class有點相似了。

WebAssembly.instantiate()

這個方法用來實例化WebAssembly代碼,注意,這裏有兩種方式使用這個方法。第一種就是,你直接給wasm的二進制數據傳給這個方法,還有一種就是你能夠調用compile方法先編譯一下,而後再去調用instantiate來實例化。

具體例子看這裏: developer.mozilla.org/zh-CN/docs/… 已經很是清楚了。

WebAssembly.instantiateStreaming()

重點來了,若是你閱讀過上篇文章,應該見過這個方法。來,上圖:

這個方法直接接收一個fetch的Response對象,直接從這裏來初始化,這就比上面的方法方便多了。

到這裏,js中加載wasm就完成了,下面看下怎麼在js中使用wasm中導出的方法。

js中訪問wasm中導出的方法

上面文章中,好像不太明顯,看不出來,從新來看個例子:

看下上面截圖中的代碼,會發現,Go寫出來的wasm,只能使用Go給你的那個js庫來運行。可是,在Go裏面,直接給js環境注入方法,這種就跟導出有點相似了,不過這裏有一點很差的是,可能會致使全局變量污染,你懂的。

看下代碼:

js.Global().Set("test", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	fmt.Println("test invoked")
	return nil
}))

signal := make(chan int)
<- signal
複製代碼

改爲以下代碼以後,將index.html裏面的js代碼修改下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="test">test</button>
</body>
<script src="./wasm_exec.js"></script>
<script>
    const go = new Go()
    WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject)
        .then(result => {
            go.run(result.instance);
            // 調用test方法
            test();
        });
</script>
</html>
複製代碼

不一樣的地方就是多了一個在go.run以後,調用了test函數,這個函數是go給我猛注入的。

看下執行結果:

到這裏你應該會發現,若是這樣直接注入全局變量或者函數的話,對應用來講,仍是不太友好的,畢竟有可能會衝突,會形成全局變量污染。這就有點坑了。那有什麼優雅點的方式呢?

仍是有的,首先,咱們修改下index.html裏面的js內容:

<script>
    // 用來提供給Go掛着導出的函數用的對象,要定義全局變量才行
    var target = {}

    const go = new Go()
    WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject)
        .then(result => {
            go.run(result.instance);
            // 調用test方法
            target.test();
        });
</script>
複製代碼

其餘內容就不貼了,跟上面的同樣。這裏有一個不一樣的點是,在第一行,定一個一個target的全局對象,而後在調用test的時候,使用了target.test()來調用。

可是你發現,target上根本沒有test方法啊,怎麼調用的。這就須要看下Go的代碼了:

js.Global().Get("target").Set("test", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	fmt.Println("test invoked")
	return nil
}))

signal := make(chan int)
<- signal
複製代碼

在Go的代碼中,首先從全局對象中獲取target對象,而後給這個對象設置一個test的函數。這樣就能夠在js中使用了。

相對於上面的直接給全局對象上設置函數以外,這種方式相對來講優雅一些,由於可以掛載的對象,咱們能夠事先定義好,對吧,這樣在Go裏面,或者js裏面,都知道這個是用來掛載Go中導出的函數的,不像上面,你是沒有辦法知道,我要怎麼訪問Go裏面導出的函數的,由於不知道去哪裏找,去全局對象上找?那你怎麼知道這個是否是GO掛載上去的呢,對吧。

wasm中訪問js中的方法

要想在wasm中訪問js中的方法,其實很簡單了,看上面的代碼應該就能猜到了,Go裏面既然能夠往js的對象上掛載函數,那確定也能直接訪問js對象上的函數。

把index.html裏面的js代碼修改下:

<script>
    var target = {
        test() {
            console.log('test method in js');
        }
    }

    const go = new Go()
    WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject)
        .then(result => {
            go.run(result.instance);
            // 調用test方法
            // target.test();
        });
</script>
複製代碼

將Go代碼修改下:

js.Global().Get("target").Get("test").Invoke();

signal := make(chan int)
<- signal
複製代碼

看下結果:

總結

這篇文章主要介紹了wasm,js中訪問wasm中導出的對象,wasm中訪問js中的對象。這裏由於使用的是Go來做爲wasm的開發語言,因此可能和別的語言有點不一樣。特別是Go裏面是直接給js的環境掛載對象,而不是像wasm規範中說的,提供exports對象,對吧。可是到這裏爲止,咱們發現,js和wasm二者已經能夠實現互相通訊了,那對於開發來講,就沒有任何阻礙了,能夠很方便的使用Go來開發wasm,而後在js中使用起來。

有任何不對的地方,歡迎指正,謝謝!🙏

相關文章
相關標籤/搜索