在以前的第一篇文章:juejin.im/post/5e9ee0… 中,大概介紹了怎麼使用Go去編寫代碼,而後將代碼編譯成wasm,以後能夠在js中使用wasm。是一個很是簡單的demo,這篇文章主要會用來說解兩個方面的知識html
原本不想介紹,由於網上已經有不少這方面的介紹了,可是爲了你們在看這系列文章的時候,能夠有個快速的瞭解,這裏仍是大概介紹下wasm。前端
特色:node
這裏簡單說一下爲何wasm執行速度快?首先,wasm更加接近機器語言,咱們知道,在計算機中運行最快的就是機器語言,由於機器能夠直接運行,不須要編譯,轉換等各類操做。wasm呢,它是一種二進制格式的文件,體積很是小,至少比js要小很是多,這樣在加載的時候,首先就比js快了對不對?其次,wasm它還能夠一邊接收服務端的返回數據,一邊就開始解碼,這你應該明白對不對?js都是要下載完整個js以後才能開始解釋執行。數組
基礎類型:瀏覽器
wasm裏面只有這4種基礎類型,因此你若是須要處理字符串,那就須要轉換爲int類型的數組,而後wasm才能處理。安全
函數支持:bash
跟其餘語言的函數同樣,沒什麼好說的。模塊化
幾個階段:函數
另外還有一點很重要,wasm它沒有本身特定的開發語言,須要你是用別的語言,好比說Go,Rust。這樣,你用Go或者Rust寫好你的代碼以後呢,將代碼編譯成wasm,這樣在js中就能使用了。post
因此,若是你正好會Go或者Rust的話,是否是優點就來了?由於至少目前來看,前端使用wasm的場景仍是很是多的。
首先介紹下js中一些關於wasm的API。具體詳細的API能夠參考: developer.mozilla.org/zh-CN/docs/…
這個對象裏面,包括了全部可使用的WebAssembly的功能。若是你的瀏覽器沒有這個對象,只能說明你用的多是遠古時期的瀏覽器。
對吧,你看這個瀏覽器兼容性就知道了,出了很是坑的IE(誰如今還要支持IE的,舉個手?),其餘瀏覽器都是支持的。
compile方法用來將wasm的二進制代碼到一個WebAssembly.Module 對象。這個應該很容易理解對吧?由於返回過來的是二進制呀,你若是須要在js中使用的話,你必需要轉換爲js中能夠操做的東西,對吧。這個方法就是幹這個事情的。
WebAssembly.Module就是經過compile()方法編譯以後,獲得輸出,可是這個輸出若是要使用呢,如今仍是不行的,還須要實例化,這個跟class有點相似了。
這個方法用來實例化WebAssembly代碼,注意,這裏有兩種方式使用這個方法。第一種就是,你直接給wasm的二進制數據傳給這個方法,還有一種就是你能夠調用compile方法先編譯一下,而後再去調用instantiate來實例化。
具體例子看這裏: developer.mozilla.org/zh-CN/docs/… 已經很是清楚了。
重點來了,若是你閱讀過上篇文章,應該見過這個方法。來,上圖:
這個方法直接接收一個fetch的Response對象,直接從這裏來初始化,這就比上面的方法方便多了。
到這裏,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中的方法,其實很簡單了,看上面的代碼應該就能猜到了,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中使用起來。
有任何不對的地方,歡迎指正,謝謝!🙏