go 基礎 WebAssembly

th.jpeg

Web開發(fā)中為什么需要 WebAssembly ,以及在實際開發(fā)中如何使用 WebAssembly喜爷?帶著這些問題開始今天分享。


question-mark1.jpg

在進入正題前我們簡單地回顧一下 web 發(fā)展的歷史

  • 第一個web 網(wǎng)頁在 1991 當時只是提供一些可以跳轉(zhuǎn)的靜態(tài)頁
  • 隨著 10 天就設計出來的 javascript 的出現(xiàn),出現(xiàn) web 應用 gmail
  • 隨后就是 javascript 庫盛行的時代
  1. 使用 jquery 操作 dom 的輕便靈巧拦英,幾乎讓我們忘記了 javascript 原生是如何操作 DOM
  2. angularjs 帶來第一個真正意義上的 SPA 的框架
  3. three.js 讓用戶在 web 端可以體驗到 3D 效果
  • 然后就是現(xiàn)代 web 時代V8WegGL平窘、Html5炉旷、serviceworks
    有了這些才讓 google docs trello 和 mirosoft365 這些原來的桌面應用成為了 web 端的應用。

不過這些還不夠地熄,沒有用根本上解決在瀏覽器端對圖形處理以及渲染還有大型計算的天生不足的問題华临。

那么首先看一看什么是WebAssembly

  • 用于 web 的全新的底層的字節(jié)碼
  • 字節(jié)碼是可以由其他語言(相對于 javascript 來說的其他語言)編譯得來
  • 可以提供 web 的性能

現(xiàn)在不僅是理論上,會給大家分享一些 webassembly 的實現(xiàn)离斩,并且會展望一些未來
首先我們來看一看 WebAssembly 究竟給我們帶來了什么银舱,以及選擇 WebAssembly 的理由。來回答第一個問題

  • 當然首先選擇了 WebAssembly 的目的就是為了提高 web 的性能跛梗,運行在瀏覽器端字節(jié)碼會更快這點毋庸置疑寻馏。所以 WebAssembly 所能夠提供的性能是無法通過 javascript 能夠?qū)崿F(xiàn)的。雖然 javascript 的引擎已經(jīng)盡可能地提高了 javascript 的性能核偿。但是 WebAssembly 在大型計算诚欠、圖形處理上還是有距離的。
  • 還有就是 WebAssembly 的友好性漾岳,可以將其他類似 c++ 的語言編譯為 WebAssembly 來使用轰绵,也可以使用開源的第三方。我們知道 c++ 可以 Android 和 ios 平臺編寫尼荆,同樣也支持 Web 端
  • 給我們編寫 Web 應用除了 javascript 以外提供更多語言的選擇(c++ go rust 這是我知道的可能更多)以后 WebAssembly 還將會支持 kotlin 和 .Net
1523691204_go-webassembly.png

現(xiàn)在我們這里用 go 語言來給大家演示我們?nèi)绾问褂?WebAssembly 來回答第二個問題左腔。
今天我?guī)Т蠹矣?go 語言來實現(xiàn)一個 hello world 的 demo。go 語言對于 web 開發(fā)者總是那么友好捅儒。


600_468861629.jpeg.png

準備工作

需要做一些準備工作安裝 gopherjs 液样,gopherjs 可以將 go 編譯為 javascript 后就可以運行在瀏覽器上。

go get -u github.com/gopherjs/gopherjs

我這里使用 gopm 這個工具下載安裝gopherjs巧还。

gopm get -v -g  github.com/gopherjs/gopherjs

搭建項目

cp $(go env GOROOT)/misc/wasm/wasm_exec.{html,js} .

通過上面的命令可以初始化以下兩個文件為:

  • wasm_exec.js
  • wasm_exec.html
WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject)

wasm_exec.html文件中鞭莽,我們看到調(diào)用assembly文件代,所以我們需要將 main.go 文件編譯為test.wasm文件麸祷,這樣在 js 中才可以訪問到 WebAssembly 所提供的方法澎怒。
下面為完整的 wasm_exec.html,可以將 html 名修改為 index.html 便于訪問阶牍。

<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>

<head>
    <meta charset="utf-8">
    <title>Go wasm</title>
</head>

<body>
    <script src="wasm_exec.js"></script>
    <script>
        if (!WebAssembly.instantiateStreaming) { // polyfill
            WebAssembly.instantiateStreaming = async (resp, importObject) => {
                const source = await (await resp).arrayBuffer();
                return await WebAssembly.instantiate(source, importObject);
            };
        }

        const go = new Go();
        let mod, inst;
        WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
            mod = result.module;
            inst = result.instance;
            document.getElementById("runButton").disabled = false;
        });

        async function run() {
            console.clear();
            await go.run(inst);
            inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
        }
    </script>

    <button onClick="run();" id="runButton" disabled>Run</button>
</body>

</html>
if (!WebAssembly.instantiateStreaming) { // polyfill
            WebAssembly.instantiateStreaming = async (resp, importObject) => {
                const source = await (await resp).arrayBuffer();
                return await WebAssembly.instantiate(source, importObject);
            };
        }

首先需要判斷WebAssembly.instantiateStreaming是否存在喷面,如果不存在我們通過其他方法實現(xiàn)WebAssembly.instantiateStreaming函數(shù)功能星瘾。
WebAssembly.instantiateStreaming異步加載以 *.wasm后綴結(jié)束的 webAssembly 的文件乖酬,成功加載test.wasm文件后取消 run 按鈕的禁用死相,run 按鈕的點擊事件是運行

WebAssembly.instantiateStreaming(fetch("test.wasm"), 

這里可以寫簡單的 hello world ,用 go run main.go 命令查看一下輸出审洞。

package main

func main() {
    println("Hello World")
}

然后通過命令下面的命令來將main.go編譯為test.wasm文件

GOARCH=wasm GOOS=js go build -o test.wasm main.go

接下來我們還需要寫一個server.go來啟動我們服務運行 wasm_exec.html创淡。

package main
import(
    "flag"
    "log"
    "net/http"
    // "strings"
)

var (
    listen = flag.String("listen",":8080","listen address")
    dir = flag.String("dir",".","directory to serve")
)

func main() {
    flag.Parse()
    log.Printf("listening on %q...",*listen)
    log.Fatal(http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir))))
    // log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request){
    //  if strings.HasSuffix(req.URL.Path,".wasm"){
    //      resp.Header().Set("content-type","application/wasm")
    //  }
    //  http.FileServer(http.Dir(*dir)).ServeHTTP(resp, req)
    // })))
}

在地址欄中輸入localhost:8080/index.html"看到我們?nèi)缦聢D,這里有一個 run 按鈕點擊就可以調(diào)用我們的 test.wasm 中輸出方法

屏幕快照 2019-04-27 下午1.17.20.png

這樣在 main.go 中輸出的hello world在瀏覽器中就成功輸出了驹止。

屏幕快照 2019-04-27 下午1.17.27.png

接下來讓 webAssembly 提供一些計算函數(shù)供 web 調(diào)用岸售,創(chuàng)建add函數(shù)接收兩個參數(shù),進行加法運算集灌。然后在js.Global()提供的 Set()方法以回調(diào)方式將 WebAssembly 提供的 add 方法掛在 javascript 的全局對象的add屬性上,這樣在 chrome 的 console 中輸入 add 就可以調(diào)用 webAssembly 的提供 add 方法梯找。

package main

import(
    "syscall/js"
)

func add(i []js.Value){
    js.Global().Set("output",js.ValueOf(i[0].Int() + i[1].Int()))
    println(js.ValueOf(i[0].Int() + i[1].Int()).String())
}

func registerCallbacks(){
    js.Global().Set("add",js.NewCallback(add))
}

func main() {
    c := make(chan struct{},0)
        println("Go WebAssembly Initialized")
    registerCallbacks()
    <-c
}
  • 在 registerCallbacks 函數(shù)中丁寄,js.Global()獲取 javascript 全局對象然后,通過 Set 方法 add 函數(shù)以回調(diào)的方式掛接到 javascript 的全局的 add 屬性上屑埋。這里是通過 js.NewCallback(add)實現(xiàn)的豪筝。

  • c := make(chan struct{},0) 創(chuàng)建 channel,然后在<-c 讓 goroutine 阻塞,以便代碼被執(zhí)行到

屏幕快照 2019-04-27 下午2.15.49.png
func subtract(i []js.Value){
    js.Global().Set("output",js.ValueOf(i[0].Int() - i[1].Int()))
    println(js.ValueOf(i[0].Int() - i[1].Int()).String())   
}

func registerCallbacks(){
    js.Global().Set("add",js.NewCallback(add))
    js.Global().Set("subtract",js.NewCallback(subtract))
}
屏幕快照 2019-04-27 下午3.24.49.png
        if (!WebAssembly.instantiateStreaming) { // polyfill
            WebAssembly.instantiateStreaming = async (resp, importObject) => {
                const source = await (await resp).arrayBuffer();
                return await WebAssembly.instantiate(source, importObject);
            };
        }

        const go = new Go();
        let mod, inst;
        WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
            mod = result.module;
            inst = result.instance;
            document.getElementById("runButton").disabled = false;
        });

        async function run() {
            console.clear();
            await go.run(inst);
            inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
        }

修改下無需點擊run我們將 test.wasm 進行加載续崖。

        const go = new Go();
        let mod, inst;
        WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then(async (result) => {
            mod = result.module;
            inst = result.instance;
            document.getElementById("runButton").disabled = false;
            await go.run(inst);
        });

        // async function run() {
        //  console.clear();

        //  inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
        // }
屏幕快照 2019-04-27 下午3.33.51.png
屏幕快照 2019-04-27 下午3.33.56.png

通過 gopherjs實現(xiàn)獲取 dom 元素的值敲街,因為類型是字符,通過strconv.Atoi方法將字符串轉(zhuǎn)為int 型進行計算严望。

func add(i []js.Value){

    value1 := js.Global().Get("document").Call("getElementById",i[0].String()).Get("value").String()
    value2 := js.Global().Get("document").Call("getElementById",i[1].String()).Get("value").String()

    int1, _ := strconv.Atoi(value1)
    int2, _ := strconv.Atoi(value2)

    js.Global().Set("output",int1 + int2)
    println(int1 + int2)
}
    <input type="text" id="value1" />
    <input type="text" id="value2" />
    <button onClick="add('value1','value2');" id="runButton">Add</button>
    <button onClick="subtract(3,2);" id="runButton">Subtract</button>
屏幕快照 2019-04-27 下午3.44.24.png

這樣做還不夠我們還需要將計算的結(jié)果輸出到 input 中多艇,所以繼續(xù)對程序改造。這里并不對 gopherjs 進行過多解釋像吻,大家可能對這些代碼有些陌生峻黍,不過稍微熟悉 web 開發(fā),這個應該不難理解一看就懂拨匆。

func add(i []js.Value){

    value1 := js.Global().Get("document").Call("getElementById",i[0].String()).Get("value").String()
    value2 := js.Global().Get("document").Call("getElementById",i[1].String()).Get("value").String()

    int1, _ := strconv.Atoi(value1)
    int2, _ := strconv.Atoi(value2)

    js.Global().Get("document").Call("getElementById",i[2].String()).Set("value",int1 + int2)
    // js.Global().Set("output",int1 + int2)
    // println(int1 + int2)
}

對應修改一下 onclick 方法的參數(shù) onClick="add('value1','value2','result');"奸披。

    <input type="text" id="value1" />
    <input type="text" id="value2" />
    <button onClick="add('value1','value2','result');" id="runButton">Add</button>
    <button onClick="subtract(3,2);" id="runButton">Subtract</button>
    <input id="result" type="text" />
屏幕快照 2019-04-27 下午3.48.59.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涮雷,隨后出現(xiàn)的幾起案子阵面,更是在濱河造成了極大的恐慌,老刑警劉巖洪鸭,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件样刷,死亡現(xiàn)場離奇詭異,居然都是意外死亡览爵,警方通過查閱死者的電腦和手機置鼻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜓竹,“玉大人箕母,你說我怎么就攤上這事【慵茫” “怎么了嘶是?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛛碌。 經(jīng)常有香客問我聂喇,道長,這世上最難降的妖魔是什么蔚携? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任希太,我火速辦了婚禮,結(jié)果婚禮上酝蜒,老公的妹妹穿的比我還像新娘誊辉。我一直安慰自己,他們只是感情好亡脑,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布堕澄。 她就那樣靜靜地躺著洲尊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奈偏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天躯护,我揣著相機與錄音惊来,去河邊找鬼。 笑死棺滞,一個胖子當著我的面吹牛裁蚁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播继准,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼枉证,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了移必?” 一聲冷哼從身側(cè)響起室谚,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崔泵,沒想到半個月后秒赤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡憎瘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年入篮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幌甘。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡潮售,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锅风,到底是詐尸還是另有隱情酥诽,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布皱埠,位于F島的核電站盆均,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漱逸。R本人自食惡果不足惜泪姨,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饰抒。 院中可真熱鬧肮砾,春花似錦、人聲如沸袋坑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至婆誓,卻和暖如春吃环,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洋幻。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工郁轻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人文留。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓好唯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親燥翅。 傳聞我的和親對象是個殘疾皇子骑篙,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容