用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 相关文章推荐
图标线性回归斜着移动到指定的位置
Aug 16 Javascript
JQuery以JSON方式提交数据到服务端示例代码
May 05 Javascript
js动态创建标签示例代码
Jun 09 Javascript
JS实现的4种数字千位符格式化方法分享
Mar 02 Javascript
JS实现从网页顶部掉下弹出层效果的方法
Aug 06 Javascript
jquery遍历table的tr获取td的值实现方法
May 19 Javascript
react-router JS 控制路由跳转实例
Jun 15 Javascript
vue中将网页打印成pdf实例代码
Jun 15 Javascript
JS中跳出循环的示例代码
Sep 14 Javascript
node中modules.exports与exports导出的区别
Jun 08 Javascript
webpack5 联邦模块介绍详解
Jul 08 Javascript
Vant 中的Toast设置全局的延迟时间操作
Nov 04 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
java EJB 加密与解密原理的一个例子
2008/01/11 PHP
PHP Yii框架之表单验证规则大全
2015/11/16 PHP
Laravel5.1 框架路由基础详解
2020/01/04 PHP
类似CSDN图片切换效果脚本
2009/09/17 Javascript
JavaScript中this的使用详解
2013/11/08 Javascript
Js 正则表达式知识汇总
2014/12/02 Javascript
基于jQuery实现带动画效果超炫酷的弹出对话框(附源码下载)
2016/02/22 Javascript
JavaScript中全选、全不选、反选、无刷新删除、批量删除、即点即改入库(在yii框架中操作)的代码分享
2016/11/01 Javascript
浅谈js中的变量名和函数名重名
2017/02/13 Javascript
BootStrap表单控件之文本域textarea
2017/05/23 Javascript
webpack打包react项目的实现方法
2018/06/21 Javascript
vue中动态设置meta标签和title标签的方法
2018/07/11 Javascript
element vue validate验证名称重复 输入框与后台重复验证 特殊字符 字符长度 及注意事项小结【实例代码】
2018/11/20 Javascript
react-native滑动吸顶效果的实现过程
2019/06/03 Javascript
JavaScript常用工具函数库汇总
2020/09/17 Javascript
[08:38]DOTA2-DPC中国联赛 正赛 VG vs Elephant 选手采访
2021/03/11 DOTA
python二叉树遍历的实现方法
2013/11/21 Python
Python计算程序运行时间的方法
2014/12/13 Python
Python格式化压缩后的JS文件的方法
2015/03/05 Python
Python多线程编程(四):使用Lock互斥锁
2015/04/05 Python
Python实现PS图像明亮度调整效果示例
2018/01/23 Python
Python实现滑动平均(Moving Average)的例子
2019/08/24 Python
Python Django框架模板渲染功能示例
2019/11/08 Python
ansible动态Inventory主机清单配置遇到的坑
2020/01/19 Python
利用Python的folium包绘制城市道路图的实现示例
2020/08/24 Python
如何利用Python matplotlib绘制雷达图
2020/12/21 Python
css3 线性渐变和径向渐变示例附图
2014/04/08 HTML / CSS
6种非常炫酷的CSS3按钮边框动画特效
2016/03/16 HTML / CSS
企划经理的岗位职责
2013/11/17 职场文书
庆七一宣传标语
2014/10/08 职场文书
2015年五一劳动节活动总结
2015/02/09 职场文书
银行招聘自荐信
2015/03/06 职场文书
裁员通知
2015/04/25 职场文书
爸爸的三轮车观后感
2015/06/16 职场文书
2016创先争优活动党员公开承诺书
2016/03/24 职场文书
怎样写工作总结啊!
2019/06/18 职场文书