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(`.`)))'
此时我们的文件夹下有四个文件
- index.html 是一个html文本
- main.go 我们的go源码
- main.wasm 用go源码编译出来的WebAssembly文件
- wasm_exec.js 从我们的goroot下复制出来的文件。
然后我们在浏览器中打开127.0.0.1:8080
,打开chrome的审查,查看console中的输出。
操作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】的按钮前
【change】的按钮后
可以看到input的值变成了我们需要的bbbbb
。
总结
WebAssembly使用起来还是挺简单的,主要是大家需要对js的相关操作需要了解,不然不知道怎么获取页面上的dom节点。当然上面的实例都是简单的,需要大家尽情的释放想象,让WebAssembly成为替代js的一个利器。