用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 相关文章推荐
JavaScript CSS修改学习第六章 拖拽
Feb 19 Javascript
超级24小时弹窗代码 24小时退出弹窗代码 100%弹窗代码(IE only)
Jun 11 Javascript
Bookmarklet实现启动jQuery(模仿 云输入法)
Sep 15 Javascript
js获取当前路径的简单示例代码
Jan 08 Javascript
javascript获取隐藏元素(display:none)的高度和宽度的方法
Jun 06 Javascript
微信小程序使用第三方库Underscore.js步骤详解
Sep 27 Javascript
jQuery图片轮播(二)利用构造函数和原型创建对象以实现继承
Dec 06 Javascript
vue移动端路由切换实例分析
May 14 Javascript
javaScript实现游戏倒计时功能
Nov 17 Javascript
js实现转动骰子模型
Oct 24 Javascript
d3.js实现图形缩放平移
Dec 19 Javascript
Vue详细的入门笔记
May 10 Vue.js
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脚本的10个技巧(5)
2006/10/09 PHP
深入php 正则表达式的学习探讨
2013/06/06 PHP
使用Discuz关键词服务器实现PHP中文分词
2014/03/11 PHP
PHP读取word文档的方法分析【基于COM组件】
2017/08/01 PHP
采用CSS和JS,刚好我最近有个站点要用到下拉菜单!
2006/06/26 Javascript
Prototype使用指南之base.js
2007/01/10 Javascript
Javascript 错误处理的几种方法
2009/06/13 Javascript
ExtJS TabPanel beforeremove beforeclose使用说明
2010/03/31 Javascript
Javascript 多浏览器兼容总结(实战经验)
2013/10/30 Javascript
Jquery 实现checkbox全选方法
2015/01/28 Javascript
详解微信小程序调起键盘性能优化
2018/07/24 Javascript
vue基于element的区间选择组件
2018/09/07 Javascript
vue 之 css module的使用方法
2018/12/04 Javascript
vue控制多行文字展开收起的实现示例
2019/10/11 Javascript
用jQuery实现抽奖程序
2020/04/12 jQuery
[01:32]TI珍贵瞬间系列(一)
2020/08/26 DOTA
[35:55]完美世界DOTA2联赛PWL S3 Rebirth vs CPG 第一场 12.11
2020/12/13 DOTA
[32:30]夜魇凡尔赛茶话会 第一期01:谁是卧底
2021/03/11 DOTA
python自动化测试之连接几组测试包实例
2014/09/28 Python
Python下使用Psyco模块优化运行速度
2015/04/05 Python
Python封装shell命令实例分析
2015/05/05 Python
Python使用正则表达式抓取网页图片的方法示例
2017/04/21 Python
Django ORM框架的定时任务如何使用详解
2017/10/19 Python
python OpenCV学习笔记之绘制直方图的方法
2018/02/08 Python
详谈pandas中agg函数和apply函数的区别
2018/04/20 Python
Flask框架踩坑之ajax跨域请求实现
2019/02/22 Python
Python搭建代理IP池实现存储IP的方法
2019/10/27 Python
如何关掉pycharm中的python console(图解)
2019/10/31 Python
Python基础之列表常见操作经典实例详解
2020/02/26 Python
Python中SQLite如何使用
2020/05/27 Python
Django如何批量创建Model
2020/09/01 Python
python em算法的实现
2020/10/03 Python
先进党支部事迹材料
2014/01/13 职场文书
亲戚结婚的请假条
2014/02/11 职场文书
高一军训决心书
2015/02/05 职场文书
html+css实现环绕倒影加载特效
2021/07/07 HTML / CSS