使用Go开发前端应用(二)

前言

在以前的第一篇文章:juejin.im/post/5e9ee0… 中,大概介绍了怎么使用Go去编写代码,而后将代码编译成wasm,以后能够在js中使用wasm。是一个很是简单的demo,这篇文章主要会用来说解两个方面的知识html

  • 简单介绍wasm
  • js中加载wasm
  • js中访问wasm中导出的方法
  • wasm中访问js中的方法

简单介绍wasm

原本不想介绍,由于网上已经有不少这方面的介绍了,可是为了你们在看这系列文章的时候,能够有个快速的了解,这里仍是大概介绍下wasm。前端

特色:node

  • 执行快速,接近机器语言,执行更加快速
  • 安全,沙箱环境
  • 与硬件无关
  • 与语言无关,没有特定开发语言,可使用其余高级语言开发,编译层wasm
  • 平台无关,能够在浏览器,node等环境运行
  • 体积更小,传输效率更高,比js
  • 模块化
  • 边接收,边解码,验证,编译
  • AOT,JIT都支持
  • 可并行化,好比解码,验证,编译能够并行执行

这里简单说一下为何wasm执行速度快?首先,wasm更加接近机器语言,咱们知道,在计算机中运行最快的就是机器语言,由于机器能够直接运行,不须要编译,转换等各类操做。wasm呢,它是一种二进制格式的文件,体积很是小,至少比js要小很是多,这样在加载的时候,首先就比js快了对不对?其次,wasm它还能够一边接收服务端的返回数据,一边就开始解码,这你应该明白对不对?js都是要下载完整个js以后才能开始解释执行。数组

基础类型:浏览器

  • i32
  • i64
  • f32
  • f64

wasm里面只有这4种基础类型,因此你若是须要处理字符串,那就须要转换为int类型的数组,而后wasm才能处理。安全

函数支持:bash

跟其余语言的函数同样,没什么好说的。模块化

几个阶段:函数

  • 编译,将二进制解码为模块的内部表现形式,实际实现能够直接编译为机器代码。
  • 验证,主要验证解码以后的结果是否正确,好比,格式,安全性等。
  • 执行
    • 实例化,定义模块的导入,初始化导出等
    • 调用,实例化完成以后,就能够调用导出的方法了

另外还有一点很重要,wasm它没有本身特定的开发语言,须要你是用别的语言,好比说Go,Rust。这样,你用Go或者Rust写好你的代码以后呢,将代码编译成wasm,这样在js中就能使用了。post

因此,若是你正好会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中使用起来。

有任何不对的地方,欢迎指正,谢谢!🙏

相关文章
相关标签/搜索