原标题:The world’s easiest introduction to WebAssembly???? 原文链接:The world’s easiest introduction to WebAssembly - freeCodeCamp.org - Medium  作者:Martin Olsansky (olso)

一个与猫咪交互的 Canvas 手机游戏,这个项目完全由 Golang 编写。图里这只小猫正在体验我编写的小游戏
  • 你认为 WebAssembly (WASM) 只用于图像处理、复杂的数学计算或者 Web 上的小小应用吗?

  • 你是否经常将 WASM 与 Web Workers 和 Service Workers 的概念混淆?

  • 你对 WASM 不感兴趣,是因为你认为现在的 Web 应用程序在未来 10 年里依旧是 JavaScript 主导?

  • 你是否想过用 JS 以外的语言做 Web 前端开发?

如果你不想细读,你可以看下我做的 demo  页面或者直接看下 ???? go-wasm-cat-game-on-canvas-with-docker 这个项目,我会讲的简洁一些,尽量不浪费你的时间。以下是我这个项目的一些关键的代码解析。

故事开始了 ????

我们的目标是给猫 ???? 做一个简单的小游戏:做一个小红点在手机上不停的移动,整个过程还有 HiFi 音乐 ????还有震动。整个项目我们会用  Golang (Go)这门语言来实现,包括 DOM 操作、逻辑还有相关的状态。

而且,由于猫咪不会使用鼠标,我们还需要给猫爪 ???? 做一些点击触摸的交互。

说一下我的理解!

把 WASM 想象成一个 通用虚拟机(UVM, Universal Virtual Machine) 或者一个沙箱,你只需编写一次任何代码,它便可以在任何地方运行。

WASM 是一个编译目标,而不是一种语言。就像你要同时针对 Windows,Mac OS 和 Linux 进行编译一样!

我不认为 WASM 会废弃 JS,你可以有其他选择而不用付出任何代价。

想象一下使用 Go,Swift,Rust,Ruby,C ++,OCaml 或者其他语言的开发人员。现在,他们可以使用自己喜欢的语言来创建交互式,联网,快速,具有脱机功能的网站和Web 应用。

你是否曾经参与过类似「一个项目是用一个代码仓库管理还是多个代码仓库管理?」问题的讨论?

好吧,不管你有没有,你现在也要想一下现在这个项目打算用一门语言实现还是多门语言实现了。

当大家可以使用相同的技术栈时,一切都会变得更加容易,尤其是团队之间的沟通。

你可以依旧使用 React 或者 Vue,但你现在开始也可以不用使用 JS 来开发了。

WASM 跟 Service Workers 还有 Web Workers 有什么区别?

Service Workers 还有 Web Workers 允许应用在后台运行,也可以做到离线运行和缓存。它们模仿线程,无法访问DOM,并且不能共享数据(仅能通过消息传递),只能在单独的上下文中运行。咦,其实我们甚至可以在其中运行 WASM 而不是 JS。对我来说,它们只提供一些具有特殊特权的抽象层,没有人说这些层必须执行 JS。

Service Workers 还有 Web Workers 是浏览器上的功能,不是 JS 的专有功能。

设置开发环境 ????

我们将使用 WASM,Go,JS 和 Docker(这个是可选的)???? 来进行开发。

如果您不了解Go,但了解 JS,请 点击这里学习 Go,然后再回来继续阅读。让我们从 Go WASM Wiki 开始。

你可以使用安装在电脑本地的  go 版本,在这里我使用 Docker 的 golang:1.12-rc 镜像。只需在此处为 go 编译器设置两个 WASM 标志。在 main.go 中创建一个简单的 hello world 进行测试。

$ GOOS=js GOARCH=wasm go build -o game.wasm main.go
build_go:docker run --rm \-v `pwd`/src:/game \--env GOOS=js --env GOARCH=wasm \golang:1.12-rc \/bin/bash -c "go build -o /game/game.wasm /game/main.go; cp /usr/local/go/misc/wasm/wasm_exec.js /game/wasm_exec.js"

现在,让我们利用好 Go 团队提供的 wasm_exec.js  代码。代码里的全局变量 Go 对 WASM 进行了初始化操作,我们不必自己从头开始做好任何 DOM 的实现。等我们编译好 wasm 文件后,它会获取 .wasm 文件并运行我们的游戏。

总而言之,它应该看起来像这样:

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><style>body{height:100%;width:100%;padding:0;margin:0;background-color:#000000;color:#FFFFFF;font-family:Arial,Helvetica,sans-serif}</style><script type="text/javascript" src="./wasm_exec.js"></script><script type="text/javascript">async function run(fileUrl) {try {const file = await fetch(fileUrl);const buffer = await file.arrayBuffer();const go = new Go();const { instance } = await WebAssembly.instantiate(buffer, go.importObject);go.run(instance);} catch (err) {console.error(err);}}setTimeout(() => run("./game.wasm"));</script></head><body></body>
</html>

放码过来!(当然是 Go 的码)

要渲染我们的这个小游戏,<canvas> 这个标签应该足够了。我们可以直接从 Go 代码创建 DOM 结构和元素!这个 syscall/js 文件 (包含在标准 Go 库中)为我们处理了与 DOM 交互的方法。

main() 方法

我敢打赌,你很久没见过 main() 方法了????。

package mainimport (// https://github.com/golang/go/tree/master/src/syscall/js"syscall/js"
)var (// js.Value 可以是任意的 JS 对象、类型或者构造函数window, doc, body, canvas, laserCtx, beep js.ValuewindowSize struct{ w, h float64 }
)func main() {setup()
}func setup() {window = js.Global()doc = window.Get("document")body = doc.Get("body")windowSize.h = window.Get("innerHeight").Float()windowSize.w = window.Get("innerWidth").Float()canvas = doc.Call("createElement", "canvas")canvas.Set("height", windowSize.h)canvas.Set("width", windowSize.w)body.Call("appendChild", canvas)// 这个是小红点 ???? Canvas 对象laserCtx = canvas.Call("getContext", "2d")laserCtx.Set("fillStyle", "red")// http://www.iandevlin.com/blog/2012/09/html5/html5-media-and-data-uri/beep = window.Get("Audio").New("data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjI1LjEwMQAAAAAAAAAAAAAA/+NAwAAAAAAAAAAAAFhpbmcAAAAPAAAAAwAAA3YAlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaW8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAExhdmYAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAN2UrY2LgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/jYMQAEvgiwl9DAAAAO1ALSi19XgYG7wIAAAJOD5R0HygIAmD5+sEHLB94gBAEP8vKAgGP/BwMf+D4Pgh/DAPg+D5//y4f///8QBhMQBgEAfB8HwfAgIAgAHAGCFAj1fYUCZyIbThYFExkefOCo8Y7JxiQ0mGVaHKwwGCtGCUkY9OCugoFQwDKqmHQiUCxRAKOh4MjJFAnTkq6QqFGavRpYUCmMxpZnGXJa0xiJcTGZb1gJjwOJDJgoUJG5QQuDAsypiumkp5TUjrOobR2liwoGBf/X1nChmipnKVtSmMNQDGitG1fT/JhR+gYdCvy36lTrxCVV8Paaz1otLndT2fZuOMp3VpatmVR3LePP/8bSQpmhQZECqWsFeJxoepX9dbfHS13/aysppUblm//8t7p2Ez7xKD/42DE4E5z9pr/nNkRw6bhdiCAZVVSktxunhxhH//4xF+bn4//6//3jEvylMM2K9XmWSn3ah1L2MqVIjmNlJtpQux1n3ajA0ZnFSu5EpXuGatn///1r/pYabq0mKT//TRyTEFNRTMuOTkuNaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+MQxNIAAANIAcAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg==")
}

看起来是不是很像 JS 代码?

是的,这就是与 DOM 交互所需的全部内容!现在只需要几个 get 方法还有调用函数即可。

awsl???? 它就在那!

在这一点上,我问自己:在某种程度上,我仍然在写 JS … 这怎么算是升级?因为我们还不能直接访问 DOM,所以我们必须(通过 JS)调用 DOM 来做任何事情。想象一下如何用 JSX / React 来抽象化它。

实际上,已经可以做到了,请期待我的下篇文章 ????。

「渲染」还有事件处理

直接使用 syscall / js 库,这个写法看起来有点像 ES5 的回调。但我们能够监听 DOM 事件,而且那些静态类型看起来很干净!

func main() {setup()// 在编译时声明渲染器var renderer js.Func// 没有错,看起来很像 JS 的回调 ????renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} {updateGame()// 实现 60FPS 的动画window.Call("requestAnimationFrame", renderer)return nil})window.Call("requestAnimationFrame", renderer)// 让我们处理下 鼠标/手势 点击事件var mouseEventHandler js.Func = js.FuncOf(func(this js.Value, args []js.Value) interface{} {updatePlayer(args[0])return nil})window.Call("addEventListener", "pointerdown", mouseEventHandler)
}func updatePlayer(event js.Value) {}
func updateGame() {}

日志记录、音频播放以及「异步」执行

在 Go 中,有一个惯例是把所有的函数都写成同步的方式,由调用者决定函数的执行是否是异步的。异步运行函数非常简单,只要在前面加上 go 就行了!它使用自己的上下文创建一个线程,你仍然可以将父级上下文绑定给它,不要担心哈。

func updatePlayer(event js.Value) {mouseX := event.Get("clientX").Float()mouseY := event.Get("clientY").Float()// `go` 关键字是主要用来实现线程、异步、并行的功能// TODO 与 Web Workers 的区别// TODO 与 Service Workers 的区别// https://gobyexample.com/goroutinesgo log("mouseEvent", "x", mouseX, "y", mouseY)// 下一个关键点if isLaserCaught(mouseX, mouseY, gs.laserX, gs.laserY) {go playSound()}
}// 不要以为我用了什么黑魔法,这里直接使用了 HTML5 的 API
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement#Basic_usage
func playSound() {beep.Call("play")window.Get("navigator").Call("vibrate", 300)
}// 这里主要用了 JS 的解构赋值语法
// 这里的 `...interface{}` 有点像 TS 的 `any` 语法
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters#Description
func log(args ...interface{}) {window.Get("console").Call("log", args...)
}

让游戏一直跑下去!

该代码创建一个非缓冲通道,并尝试从该通道接收数据。因为没有人向它发送任何东西,它本质上是一个永久的阻塞操作,允许我们永远运行我们的程序。

func main() {// https://stackoverflow.com/a/47262117// 创建空通道runGameForever := make(chan bool)setup()// 尝试从空通道接收// 由于没有人向它发送任何数据,它本质上是一个永久阻塞操作// 我们有一个 daeomon / service / background 程序// 在 WASM 里,我们的游戏会一直运行 ????<-runGameForever
}

更新游戏状态并移动小红点

这里没有状态管理,只有一个简单的声明类型的结构体,它不允许在内部传递任何不正确的值。

import ("math"
)type gameState struct{ laserX, laserY, directionX, directionY, laserSize float64 }var (// gs 处于最高范围,小于这个范围小红点 ???? 都能都能访问gs = gameState{laserSize: 35, directionX: 3.7, directionY: -3.7, laserX: 40, laserY: 40}
)func updateGame() {// 边界判断if gs.laserX+gs.directionX > windowSize.w-gs.laserSize || gs.laserX+gs.directionX < gs.laserSize {gs.directionX = -gs.directionX}if gs.laserY+gs.directionY > windowSize.h-gs.laserSize || gs.laserY+gs.directionY < gs.laserSize {gs.directionY = -gs.directionY}// 移动小红点 ????gs.laserX += gs.directionXgs.laserY += gs.directionY// 清除画布laserCtx.Call("clearRect", 0, 0, windowSize.w, windowSize.h)//画一个小红点 ????laserCtx.Call("beginPath")laserCtx.Call("arc", gs.laserX, gs.laserY, gs.laserSize, 0, math.Pi*2, false)laserCtx.Call("fill")laserCtx.Call("closePath")
}// 判断点击的点是不是在小红点 ???? 内部
func isLaserCaught(mouseX, mouseY, laserX, laserY float64) bool {// 直接这样返回是不行的// return laserCtx.Call("isPointInPath", mouseX, mouseY).Bool()// 所以这里我通过勾股定理 ???? 来实现// 同时我给 laserSize 属性的值加上 15,让猫爪更容易点击  ????return (math.Pow(mouseX-laserX, 2) + math.Pow(mouseY-laserY, 2)) < math.Pow(gs.laserSize+15, 2)
}

总结

事实上,WASM 仍然被认为是一个 [MVP](https://hacks.mozilla.org/2018/10/webassembly -post- MVP -future/) (MAP),你可以不用编写一行 JS,就能创建一个像这样的游戏。惊不惊讶!CanIUse 上 WASM 的支持已经是一片绿色了,没有人可以阻止你去创建基于 WASM 的网站和应用。

你可以组合所有你想要的语言,像是把 JS 转成 WASM。最后,它们都将编译成 WASM 字节码。如果你需要在他们之间分享任何东西,也没问题,因为它们可以共享原始内存。

我担心的是,在最近的新闻中,我们关注到 微软正在开发 Chromium 浏览器 还有 Firefox市场份额低于9%。这使谷歌在 WASM 上有了致命的切换能力。如果他们不愿意配合,大众可能永远不会知道有这个特性。

你看这只猫玩的多开心 ????

现在都有谁在用 WASM?

你必须得承认,我的项目已经在用了。这个项目仅仅是画了一个全屏的画布,这里有一些更高级的例子,它们关注于语义 Web awesome-wasm#web-frameworks-libraries。

同时,也有相当多的项目已经上了 WASM 的车了。我对 Spotify、Twitch 和 Figma 和 EWASM 更感兴趣。

Web3 时代的 WASM

现在,如果你想在手机上使用以太坊钱包(Ethereum wallet),你必须从应用商店下载一个类似于 Status.im 的移动端钱包 App,并且信任所有商家。

如果有一个先进的 Web App,可以运行 geth (Go Ethereum 客户端),并且能在 WebRTC 上光速同步,这会怎么样?它可以使用 Service Worker 来更新它的 WASM 代码并在后台运行,可以托管在 IPFS/Dat 上。

一些有用的关于 WASM 的文章、资源还有学习资料 ????

  • WebAssembly is more than the web

  • WebAssembly and Go: A look at the future  还有  HN comments

  • Mozilla Hacks  和 Hacker News 发布的文章

  • WebAssembly architecture for Goawesome-wasm , awesome-wasm-langs ,  gowasm-experiments ,  WasmWeekly , WasmRocks ,  SPA with C++ ,  better DOM bindings for Go

感谢 twifkak 在 Android Chrome 上对 Go 的优化!

可能是世界上最简单的用 Go 来写 WebAssembly 的教程相关推荐

  1. 2016第18本:世界上最简单的会计书

    从周叶的微信公众号zyea_com中发现了这本书<世界上最简单的会计书>,自己也听说过资产负债表.损益表和现金流量表等名词,但从来没搞清楚过这些表格的意思.所以读此书的目的是想着理财过程中 ...

  2. 世界上最简单的会计书—读书笔记

    世界上最简单的会计书 美 Darrell Mullis, Judith Orloff 前言 本书以一个柠檬汁摊的前世今生,讲述了经营一个企业的过程中会遇到的各种各样与会计相关的知识.正如本书页面文字那 ...

  3. 最赚钱的日内交易策略_百万美金交易员分享:我认为这是世界上最简单的外汇交易策略...

    有人说,"如果拉长时间周期来看,在外汇交易里,各种交易策略更像是一种安慰剂及心理暗示,而没有实质的作用,对于许多外汇交易者来说,交易策略更像是一种手段,让自己能够继续理智判断下去的交易手段. ...

  4. 一组数字中算出最相近的组合_机器学习有意思! 世界上最简单的机器学习入门...

    你是否也曾听人们谈起机器学习但是只有一个朦胧的概念?你是否厌倦了在同事的高谈阔论中颓然欲睡?此诚求变之机. 本教程适合所有对机器学习感到好奇,却不知从何下手的读者.我想应该有很多人试着读了维基百科页面 ...

  5. 《机器学习有意思! 01》- 世界上最简单的机器学习入门

    本文首发:https://jizhi.im/blog/post/ml_is_fun_01 你是否也曾听人们谈起机器学习但是只有一个朦胧的概念?你是否厌倦了在同事的高谈阔论中颓然欲睡?此诚求变之机. 本 ...

  6. 世界上最简单的会计书(服务行业利润表)

    上周情况 交易明细 本周服务业情况 1.客户(休阿姨和埃里克叔叔)购买了每天8美元共3天的咨询服务.你派了两位咨询师前往做咨询服务.所以,收入总额是多少?48美元. 2.对于咨询服务,你投入了什么?你 ...

  7. 《世界上最简单的会计学》

    1.若我们假设财务记账好比一个凳子,资产负债表是其中的一条腿,利润表是另一条腿,我们还需要一条腿来使凳子平衡.它就是现金流量表. 2."欠条"在商业上被称做应付票. 3.负债(我们 ...

  8. 世界上最简单的会计书(资产负债表)

    上周情况 上周交易明细 第二周交易明细 1.你的柠檬汁摊成功开张一周后,你决定去拜访一下银行.你向银行出示了你一周的财务报表,他们贷款给你50美元现金. 2.你决定给自己半天的假期,于是将剩余的柠檬汁 ...

  9. 世界上最简单的会计书(现金流量表)

    上周交易连接 交易明细 本周交易明细 1.收到朋友的代收账款5美元 2.以8美元的价格收购了朋友家的柠檬汁摊位,另外支付了2美元的场地费,共支出10美元(商业中财产.厂房和设备通常称为固定资产) 3. ...

最新文章

  1. 不存在从void转换到sqlist的适当构造函数_拷贝构造函数与赋值构造函数
  2. linux webservice服务器端,Linux查看资源使用情况 webservice服务端口监控
  3. leetcode310. Minimum Height Trees
  4. case when用法java_Oracle CASE WHEN 用法介绍
  5. Linux挂载windows中的共享目录步骤及问题解决方案(步骤清晰)
  6. php 判断同时存在英文跟数字,php判断输入是否是纯数字,英文,汉字的方法
  7. 关于MySql的1146错误修正
  8. linux 7安装mysql8.12_Linux(centOS 7)安装MySQL8.12
  9. android百度定位代码,android开发:百度地图及定位的演示代码
  10. 程序流程三控制,顺序控制,分支控制,循环控制综合练习题
  11. 图灵 计算机 ppt,人工智能导论(ppt 155页)
  12. floyd算法----牛栏
  13. Win10任务栏图标变成空白方块解决办法
  14. linux嵌入式计算器绪论,毕业设计—嵌入式计算器
  15. 跳跃表skiplist简析
  16. c++---constructor(ctor,构造函数)
  17. 什么是静态的html页面,什么是静态页面?什么是动态页面?二者有什么区别
  18. 拜托,使用 Three.js 让二维图片具有 3D 效果超酷的
  19. == 和 === 有什么区别?
  20. 《信号与系统学习笔记》—拉普拉斯变换(二)

热门文章

  1. 惠普大佬:未来30年四大趋势将推动科技产业发展
  2. 第五六周机电传动控制作业
  3. Android SoundPool 的简单使用
  4. 关于iframe的contentDocument和contentWindow
  5. Windows7 Search Federation功能解读
  6. HDU多校1 - 6959 zoto(莫队+树状数组/值域分块)
  7. CodeForces - 86D Powerful array(莫队)
  8. linux排序语言,Go语言排序sort的使用
  9. TIME_WAIT简介
  10. 一个DDOS病毒的分析(二)