内容简介:在之前的第一篇文章:本来不想介绍,因为网上已经有很多这方面的介绍了,但是为了大家在看这系列文章的时候,可以有个快速的了解,这里还是大概介绍下wasm。特点:
在之前的第一篇文章: 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中使用起来。
有任何不对的地方,欢迎指正,谢谢!:pray:
欢迎关注我们的微信公众号,每天学习Go知识
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端技术演进(四):前端三层结构与应用
- 前端应用框架 - NoahV
- 使用Go开发前端应用
- 如何使用 docker 部署前端应用
- 【架构拾集】 微前端:微应用化
- 前端之路:紧跟潮流,docker简单应用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编程的修炼(中英双语)
[荷]Edsger W. Dijkstra / 裘宗燕 / 电子工业出版社 / 2013-7 / 79.00元
本书是图灵奖获得者Edsger W. Dijkstra在编程领域里的经典著作中的经典。作者基于其敏锐的洞察力和长期的实际编程经验,对基本顺序程序的描述和开发中的许多关键问题做了独到的总结和开发。书中讨论了顺序程序的本质特征、程序描述和对程序行为(正确性)的推理,并通过一系列从简单到复杂的程序的思考和开发范例,阐释了基于严格的逻辑推理开发正确可靠程序的过程。 本书写于20世纪70年代中后期,但......一起来看看 《编程的修炼(中英双语)》 这本书的介绍吧!