用Golang运行JavaScript的实现示例


Posted in Javascript onNovember 25, 2019

C++太麻烦(难)了,想要盘弄一下V8实在是有些费劲,但是Golang社区出了几个Javascript引擎,要尝试在别的语言中如何集成Javascript,是个不错的选择。以下选了github.com/dop251/goja 来做例子。

Hello world

照着仓库的Readme,来一个:

package main

import (
 "fmt"
 js "github.com/dop251/goja"
)

func main() {
 vm := js.New() // 创建engine实例
 r, _ := vm.RunString(`
  1 + 1
 `) // 执行javascript代码
 v, _ : = r.Export().(int64) // 将执行的结果转换为Golang对应的类型
 fmt.Println(r)
}

这个例子展示了最基本的能力,给定一段Javascript的代码文本,它能执行得到一个结果,并且能得到执行结果的宿主语言的表示形式。

交互

Javascript和Golang之间的交互分成两个方面:Golang向Javascript引擎中注入一些上下文,例如注册一些全局函数供Javascript使用,创建一个对象等;Golang从Javascript引擎中读取一些上下文,例如一个计算过程的计算结果。先看第一类。

常用的手段是,通过Runtime类型提供的Set方法在全局注册一个变量,例如

...
rts := js.New()
rts.Set("x", 2)
rts.RunString(`x+x`) // 4
...

此处Set的方法签名是func (r *Runtime) Set(name string, value interface{}),对于基本类型,不需要额外的包装,就可以自动转换,但是当需要传递一个复杂对象时,需要用NewObject包装一下:

rts := js.New()
o := rts.NewObject()
o.Set("x", 2)
rts.Set("o", o)
rts.RunString(`o.x+o.x`) // 4

切换到Golang的视角,是个很自然的过程,想要创建一个对象,需要在Golang中先创建一个对应的表述,然后在Javascript中才能使用。对于更复杂的对象,嵌套就好了。

定义函数则有所不同,不同之处在于Javascript中的函数在Golang中的表示和其它类型的值不太一样,Golang中表式Javascript中的函数的签名为:func (js.FunctionCall) js.Value,js.FunctionCall中包含了调用函数的上下文信息,基于此我们可以尝试给Javascript增加一个console.log的能力:

...
func log(call js.FunctionCall) js.Value {
  str := call.Argument(0)
  fmt.Print(str.String())
  return str
}
...
rts := js.New()
console := rts.NewObject()
console.Set("log", log)
rts.Set("console", console)
rts.RunString(`console.log('hello world')`) // hello world

相较于向Javascript引擎中注入一些信息,从中读取信息则比较简单,前面的hello world中展示了一种方法,执行一段Javascript代码,然后得到一个结果。但是这种方法不够灵活,如果想要精确的得到某个上下文,变量的值,就不那么方便。为此,goja提供了Get方法,Runtime类型的Get方法可以从Runtime中读取某个变量的信息,Object类型的Get方法则可以从对象中读取某个字段的值。签名如下:func (r *Runtime) Get(name string) Value,func (o *Object) Get(name string) Value。但是得到的值的类型都是Value类型,想要转换成对应的类型,需要通过一些方法来转换,这里就不再赘述,有兴趣可以去看它的文档。

一个复杂些的例子

goja值提供了基本的解析执行Javascript代码的能力,但是我们常见的宿主提供的能力,需要在使用的过程中自己去补充。下面就基于上面的技巧,提供一个简单的require加载本地Javascript代码的能力。

通过require加载一段Commjs格式Javascript代码,直观的流程:根据文件名,读取文本,组装成一个立即执行函数,执行,然后返回module对象,但是中间可以做一些小优化,比如已经被加载过的代码, 就不重新加载,执行,只是返回就好了。大概的实现如下:

package core

import (
  "io/ioutil"
  "path/filepath"

  js "github.com/dop251/goja"
)

func moduleTemplate(c string) string {
  return "(function(module, exports) {" + c + "\n})"
}

func createModule(c *Core) *js.Object {
  r := c.GetRts()
  m := r.NewObject()
  e := r.NewObject()
  m.Set("exports", e)

  return m
}

func compileModule(p string) *js.Program {
  code, _ := ioutil.ReadFile(p)
  text := moduleTemplate(string(code))
  prg, _ := js.Compile(p, text, false)

  return prg
}

func loadModule(c *Core, p string) js.Value {
  p = filepath.Clean(p)
  pkg := c.Pkg[p]
  if pkg != nil {
    return pkg
  }

  prg := compileModule(p)

  r := c.GetRts()
  f, _ := r.RunProgram(prg)
  g, _ := js.AssertFunction(f)

  m := createModule(c)
  jsExports := m.Get("exports")
  g(jsExports, m, jsExports)

  return m.Get("exports")
}
要想让引擎能使用这个能力,就需要将require这个函数注册到Runtime中,

// RegisterLoader register a simple commonjs style loader to runtime
func RegisterLoader(c *Core) {
  r := c.GetRts()

  r.Set("require", func(call js.FunctionCall) js.Value {
    p := call.Argument(0).String()
    return loadModule(c, p)
  })
}

完整的例子有兴趣可看github.com/81120/gode

写在后面

之前一直分不清Javascript引擎和Javascript执行环境的界限,通过这个例子,有了一个很具体的认识。而且,对Node本身的结构也有了一个更清楚的认知。在一些场景下,需要将一些语言嵌入到另一个语言中实现一些更灵活的功能和解耦,例如nginx中的lua,游戏引擎中的lua,mongodb shell中的Javascipt,甚至nginx官方头提供了一个阉割版本的Javascript实现作为配置的DSL。那么在这种需要嵌入DSL的场景下,嵌入一个成熟语言的执行引擎比自己实现一个DSL要简单方便得多。而且,各种场景下,对语言本身的要求也不尽相同,例如边缘计算场景,嵌入式下,可以用Javascript来开发,但是是不是需要一个完整的V8呢?对环境和性能有特殊要求的场景下,限制DSL,提供必要的宿主语言扩展也是个不错的思路吧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
FormValid0.5版本发布,带ajax自定义验证例子
Aug 17 Javascript
获取URL地址中的文件名和参数的javascript代码
Sep 02 Javascript
页面装载js及性能分析方法介绍
Mar 21 Javascript
微信分享的标题、缩略图、连接及描述设置方法
Oct 14 Javascript
js实现可折叠展开的手风琴菜单效果
Sep 07 Javascript
node.js从数据库获取数据
May 08 Javascript
bootstrap-table实现服务器分页的示例 (spring 后台)
Sep 01 Javascript
小程序视频列表中视频的播放与停止的示例代码
Jul 20 Javascript
vue基础之模板和过滤器用法实例分析
Mar 12 Javascript
vue解决花括号数据绑定不成功的问题
Oct 30 Javascript
JS+Canvas实现五子棋游戏
Aug 26 Javascript
解决antd 表单设置默认值initialValue后验证失效的问题
Nov 02 Javascript
JS插入排序简单理解与实现方法分析
Nov 25 #Javascript
纯 JS 实现放大缩小拖拽功能(完整代码)
Nov 25 #Javascript
python实现迭代法求方程组的根过程解析
Nov 25 #Javascript
JS桶排序的简单理解与实现方法示例
Nov 25 #Javascript
JavaScript交换两个变量方法实例
Nov 25 #Javascript
three.js利用gpu选取物体并计算交点位置的方法示例
Nov 25 #Javascript
基于javascript实现贪吃蛇小游戏
Nov 25 #Javascript
You might like
PHP执行速率优化技巧小结
2008/03/15 PHP
PHP getallheaders无法获取自定义头(headers)的问题
2016/03/23 PHP
getElementsByTagName vs selectNodes效率 及兼容的selectNodes实现
2010/02/26 Javascript
理解Javascript_02_理解undefined和null
2010/10/11 Javascript
Textarea与懒惰渲染实现代码
2012/01/04 Javascript
js实时获取系统当前时间实例代码
2013/06/28 Javascript
查找iframe里元素的方法可传参
2013/09/11 Javascript
jQuery中的jQuery()方法用法分析
2014/12/27 Javascript
jquery实现用户打分评分特效
2015/05/28 Javascript
基于javascript简单实现对身份证校验
2021/01/25 Javascript
初识angular框架后的所思所想
2016/02/19 Javascript
url传递的参数值中包含&时,url自动截断问题的解决方法
2016/08/02 Javascript
JS实现超简单的汉字转拼音功能示例
2016/12/22 Javascript
JS中input表单隐藏域及其使用方法
2017/02/13 Javascript
JavaScript实现公历转农历功能示例
2017/02/13 Javascript
微信小程序框架wepy之动态控制类名
2018/09/14 Javascript
Vue.js中 v-model 指令的修饰符详解
2018/12/03 Javascript
VUE写一个简单的表格实例
2019/08/06 Javascript
JavaScript forEach中return失效问题解决方案
2020/06/01 Javascript
[46:58]完美世界DOTA2联赛PWL S3 Forest vs LBZS 第一场 12.17
2020/12/19 DOTA
[06:07]DOTA2-DPC中国联赛3月5日Recap集锦
2021/03/11 DOTA
python模块之time模块(实例讲解)
2017/09/13 Python
python3+PyQt5 使用三种不同的简便项窗口部件显示数据的方法
2019/06/17 Python
python opencv 实现对图像边缘扩充
2020/01/19 Python
pycharm导入源码的具体步骤
2020/08/04 Python
Python私有属性私有方法应用实例解析
2020/09/15 Python
html5设计原理(推荐收藏)
2014/05/17 HTML / CSS
canvas 下载二维码和图片加水印的方法
2018/03/21 HTML / CSS
ColourPop美国官网:卡拉泡泡,洛杉矶彩妆品牌
2019/04/28 全球购物
元旦晚会感言
2014/03/12 职场文书
经济信息系毕业生自荐信
2014/06/02 职场文书
2015应届毕业生求职信范文
2015/03/20 职场文书
违反纪律检讨书范文
2015/05/07 职场文书
60条职场经典语录,总有一条能触动你的心
2019/08/21 职场文书
使用ORM新增数据在Mysql中的操作步骤
2021/07/26 MySQL
如何基于python实现单目三维重建详解
2022/06/25 Python