前言
在之前的第一篇文章:juejin.im/post/5e9ee0… 中,大概介绍了怎么使用Go去编写代码,然后将代码编译成wasm,之后可以在js中使用wasm。是一个非常简单的demo,这篇文章主要会用来讲解两个方面的知识
- 简单介绍wasm
- js中加载wasm
- js中访问wasm中导出的方法
- wasm中访问js中的方法
简单介绍wasm
本来不想介绍,因为网上已经有很多这方面的介绍了,但是为了大家在看这系列文章的时候,可以有个快速的了解,这里还是大概介绍下wasm。
特点:
- 执行快速,接近机器语言,执行更加快速
- 安全,沙箱环境
- 与硬件无关
- 与语言无关,没有特定开发语言,可以使用其他高级语言开发,编译层wasm
- 平台无关,可以在浏览器,node等环境运行
- 体积更小,传输效率更高,比js
- 模块化
- 边接收,边解码,验证,编译
- AOT,JIT都支持
- 可并行化,比如解码,验证,编译可以并行执行
这里简单说一下为什么wasm执行速度快?首先,wasm更加接近机器语言,我们知道,在计算机中运行最快的就是机器语言,因为机器可以直接运行,不需要编译,转换等各种操作。wasm呢,它是一种二进制格式的文件,体积非常小,至少比js要小非常多,这样在加载的时候,首先就比js快了对不对?其次,wasm它还可以一边接收服务端的返回数据,一边就开始解码,这你应该明白对不对?js都是要下载完整个js之后才能开始解释执行。
基础类型:
- i32
- i64
- f32
- f64
wasm里面只有这4种基础类型,所以你如果需要处理字符串,那就需要转换为int类型的数组,然后wasm才能处理。
函数支持:
跟其他语言的函数一样,没什么好说的。
几个阶段:
- 编译,将二进制解码为模块的内部表现形式,实际实现可以直接编译为机器代码。
- 验证,主要验证解码之后的结果是否正确,比如,格式,安全性等。
- 执行
- 实例化,定义模块的导入,初始化导出等
- 调用,实例化完成之后,就可以调用导出的方法了
另外还有一点很重要,wasm它没有自己特定的开发语言,需要你是用别的语言,比如说Go,Rust。这样,你用Go或者Rust写好你的代码之后呢,将代码编译成wasm,这样在js中就能使用了。
所以,如果你正好会Go或者Rust的话,是不是优势就来了?因为至少目前来看,前端使用wasm的场景还是非常多的。
js中加载wasm
首先介绍下js中一些关于wasm的API。具体详细的API可以参考: developer.mozilla.org/zh-CN/docs/…
WebAssembly 全局对象
这个对象里面,包括了所有可以使用的WebAssembly的功能。如果你的浏览器没有这个对象,只能说明你用的可能是远古时期的浏览器。
对吧,你看这个浏览器兼容性就知道了,出了非常坑的IE(谁现在还要支持IE的,举个手?),其他浏览器都是支持的。
WebAssembly.compile()
compile方法用来将wasm的二进制代码到一个WebAssembly.Module 对象。这个应该很容易理解对吧?因为返回过来的是二进制呀,你如果需要在js中使用的话,你必须要转换为js中可以操作的东西,对吧。这个方法就是干这个事情的。
WebAssembly.Module
WebAssembly.Module就是通过compile()方法编译之后,得到输出,但是这个输出如果要使用呢,现在还是不行的,还需要实例化,这个跟class有点类似了。
WebAssembly.instantiate()
这个方法用来实例化WebAssembly代码,注意,这里有两种方式使用这个方法。第一种就是,你直接给wasm的二进制数据传给这个方法,还有一种就是你可以调用compile方法先编译一下,然后再去调用instantiate来实例化。
具体例子看这里: developer.mozilla.org/zh-CN/docs/… 已经非常清楚了。
WebAssembly.instantiateStreaming()
重点来了,如果你阅读过上篇文章,应该见过这个方法。来,上图:
这个方法直接接收一个fetch的Response对象,直接从这里来初始化,这就比上面的方法方便多了。
到这里,js中加载wasm就完成了,下面看下怎么在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中的方法
要想在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中使用起来。
有任何不对的地方,欢迎指正,谢谢!????