WebAssembly初探Golang篇

发布于 2019-11-15 22:48:42 阅读 962

Web领域现状

众所周知,浏览器常使用的是JS引擎,它是解释性执行的脚本语言,所以需要在程序运行过程中被逐行地解释,在某些情况下会比较耗费性能,在大型项目中的性能问题就会体现出来。

最近非常火的Flutter使用的Dart语言也是通过新的虚拟机去直接运行 Dart 程序以提升性能。但是Dart语言的web版本现在还在内测,暂时不推荐大家使用,建议等到时机成熟时再去了解。

什么是WebAssembly

为了解决JS语言的性能问题,WebAssembly出现了,它是一种新的字节码格式,主流浏览器都已经支持 WebAssembly。 和 JS 需要解释执行不同的是,WebAssembly 字节码和底层机器码很相似可快速装载运行,因此性能相对于 JS 解释执行大大提升。 也就是说 WebAssembly 并不是一门编程语言,而是一份字节码标准,需要用高级编程语言编译出字节码放到 WebAssembly 虚拟机中才能运行, 浏览器厂商需要做的就是根据 WebAssembly 规范实现虚拟机。

Golang教程实战

本文将使用golang来写出一份在浏览器上执行的WebAssembly。本教程是从Golang 官方教程中得出。

需要golang 版本>=1.3

在浏览器输出hello WebAssembly

首先我们需要创建一个main.go文件

package main
import "fmt"
func main() {
    fmt.Println("Hello, WebAssembly!")
}

然后执行 GOOS=js GOARCH=wasm go build -o main.wasm 将main.go文件编译成WebAssembly的执行文件,格式为wasm。

其中

  • GOOS:目标可执行程序运行操作系统,支持darwin(macOS),freebsd,linux,windows,js(浏览器)
  • GOARCH:目标可执行程序操作系统构架,支持386,amd64,arm,wasm(就是本场主角WebAssembly)

将wasm_exec.js文件复制到当前目录下

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

wasm_exec.js的工作就是让浏览器可以执行wasm格式文件。

再创建一个index.html

<html>
    <head>
        <meta charset="utf-8"/>
        <script src="wasm_exec.js"></script>
        <script>
            const go = new Go();
            WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
                go.run(result.instance);
            });
        </script>
    </head>
    <body></body>
</html>

然后执行

go get -u github.com/shurcooL/goexec
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

此时我们的文件夹下有四个文件

C7435C3E-7034-4478-B79A-D02AC55672F1.png

  • index.html 是一个html文本
  • main.go 我们的go源码
  • main.wasm 用go源码编译出来的WebAssembly文件
  • wasm_exec.js 从我们的goroot下复制出来的文件。

然后我们在浏览器中打开127.0.0.1:8080,打开chrome的审查,查看console中的输出。

20191116003254.jpg

操作DOM

那么如何使用WebAssembly操作DOM呢?

我们修改main.go文件

package main

import (
    "fmt"
    "syscall/js"
)

func main() {
    c := make(chan struct{}, 0)
    document := js.Global().Get("document")
    nameValue := document.Call("getElementById", "name")

    btnCallback := js.FuncOf(func(this js.Value, i []js.Value) interface{} {
        nameValue.Set("value", "bbbbb")
        return nil
    })

    btn := document.Call("getElementById", "change-btn")
    btn.Call("addEventListener", "click", btnCallback)
    fmt.Println("WebAssembly start...")
    <-c
}

记得重新生成新的wasm文件GOARCH=wasm GOOS=js go build -o main.wasm main.go

其中js.Global().Get("document")如同js中的this,然后调用document.Call("getElementById", "name")就如同使用js的this.getElementById("name")一个道理。

那为什么需要一个channel呢?

因为我们需要监听addEventListener操作,如果不适用channel阻塞这个方法,那么我们的点击事件就无效,并且会报下面这个错误

wasm_exec.js:484 Uncaught Error: Go program has already exited
    at global.Go._resume (wasm_exec.js:484)
    at HTMLButtonElement.<anonymous> (wasm_exec.js:497)

再修改index.html文件

<html>
    <head>
        <meta charset="utf-8"/>
        <script src="wasm_exec.js"></script>
        <script>
            const go = new Go();
            WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
                go.run(result.instance);
            });
        </script>
    </head>
    <body>
        <input type="text" id="name" value="click me to change">
        <button id="change-btn">change</button>
    </body>
</html>

打开浏览器,点击这个【change】的按钮前

20191116004314.jpg

【change】的按钮后

20191116004328.jpg

可以看到input的值变成了我们需要的bbbbb

总结

WebAssembly使用起来还是挺简单的,主要是大家需要对js的相关操作需要了解,不然不知道怎么获取页面上的dom节点。当然上面的实例都是简单的,需要大家尽情的释放想象,让WebAssembly成为替代js的一个利器。