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

zhongzhong05 · · 1781 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前言

在之前的第一篇文章: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中使用起来。

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


有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:zhongzhong05

查看原文:使用Go开发前端应用(二)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1781 次点击  
加入收藏 微博
1 回复  |  直到 2020-05-01 15:06:45
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传