程序员GoGolang

go 基础 WebAssembly

2019-04-27  本文已影响24人  zidea
th.jpeg

Web开发中为什么需要 WebAssembly ,以及在实际开发中如何使用 WebAssembly?带着这些问题开始今天分享。


question-mark1.jpg

在进入正题前我们简单地回顾一下 web 发展的历史

  1. 使用 jquery 操作 dom 的轻便灵巧,几乎让我们忘记了 javascript 原生是如何操作 DOM
  2. angularjs 带来第一个真正意义上的 SPA 的框架
  3. three.js 让用户在 web 端可以体验到 3D 效果

不过这些还不够,没有用根本上解决在浏览器端对图形处理以及渲染还有大型计算的天生不足的问题。

那么首先看一看什么是WebAssembly

现在不仅是理论上,会给大家分享一些 webassembly 的实现,并且会展望一些未来
首先我们来看一看 WebAssembly 究竟给我们带来了什么,以及选择 WebAssembly 的理由。来回答第一个问题

1523691204_go-webassembly.png

现在我们这里用 go 语言来给大家演示我们如何使用 WebAssembly 来回答第二个问题。
今天我带大家用 go 语言来实现一个 hello world 的 demo。go 语言对于 web 开发者总是那么友好。


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} .

通过上面的命令可以初始化以下两个文件为:

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

wasm_exec.html文件中,我们看到调用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是否存在,如果不存在我们通过其他方法实现WebAssembly.instantiateStreaming函数功能。
WebAssembly.instantiateStreaming异步加载以 *.wasm后缀结束的 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"看到我们如下图,这里有一个 run 按钮点击就可以调用我们的 test.wasm 中输出方法

屏幕快照 2019-04-27 下午1.17.20.png
这样在 main.go 中输出的hello world在浏览器中就成功输出了。 屏幕快照 2019-04-27 下午1.17.27.png

接下来让 webAssembly 提供一些计算函数供 web 调用,创建add函数接收两个参数,进行加法运算。然后在js.Global()提供的 Set()方法以回调方式将 WebAssembly 提供的 add 方法挂在 javascript 的全局对象的add属性上,这样在 chrome 的 console 中输入 add 就可以调用 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
}
屏幕快照 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实现获取 dom 元素的值,因为类型是字符,通过strconv.Atoi方法将字符串转为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

这样做还不够我们还需要将计算的结果输出到 input 中,所以继续对程序改造。这里并不对 gopherjs 进行过多解释,大家可能对这些代码有些陌生,不过稍微熟悉 web 开发,这个应该不难理解一看就懂。

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 方法的参数 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
上一篇 下一篇

猜你喜欢

热点阅读