用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 相关文章推荐
漂亮的提示信息(带箭头)
Mar 21 Javascript
javascript之更有效率的字符串替换
Aug 02 Javascript
JavaScript类和继承 constructor属性
Mar 04 Javascript
工作中常用到的JS表单验证代码(包括例子)
Nov 11 Javascript
JavaScript 学习笔记之一jQuery写法图片等比缩放以及预加载
Jun 28 Javascript
window.print打印指定div实例代码
Dec 13 Javascript
Boostrap入门准备之border box
May 09 Javascript
一次$.getJSON不执行的简单记录
Jul 19 Javascript
Vue.js实战之组件之间的数据传递
Apr 01 Javascript
javascript代码优化的8点总结
Jan 29 Javascript
bootstrap 路径导航 分页 进度条的实例代码
Aug 06 Javascript
如何基于layui的laytpl实现数据绑定的示例代码
Apr 10 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
PHP6 先修班 JSON实例代码
2008/08/23 PHP
php利用腾讯ip分享计划获取地理位置示例分享
2014/01/20 PHP
PHP如何获取当前主机、域名、网址、路径、端口等参数
2017/06/09 PHP
PHP实现的XXTEA加密解密算法示例
2018/08/28 PHP
广告显示判断
2006/08/31 Javascript
Javascript中的delete介绍
2012/09/02 Javascript
jQuery阻止同类型事件小结
2013/04/19 Javascript
JQuery ztree 异步加载实例讲解
2016/02/25 Javascript
浅谈JavaScript事件绑定的常用方法及其优缺点分析
2016/11/01 Javascript
js数组去重的hash方法
2016/12/22 Javascript
基于Node的React图片上传组件实现实例代码
2017/05/10 Javascript
vue中用H5实现文件上传的方法实例代码
2017/05/27 Javascript
详解webpack2+React 实例demo
2017/09/11 Javascript
微信小程序实现简单input正则表达式验证功能示例
2017/11/30 Javascript
js和jQuery以及easyui实现对下拉框的指定赋值方法
2018/01/23 jQuery
JS装饰器函数用法总结
2018/04/21 Javascript
解决vue 引入子组件报错的问题
2018/09/06 Javascript
Vue指令指令大全
2019/02/09 Javascript
node命令行工具之实现项目工程自动初始化的标准流程
2019/08/12 Javascript
基于vue-cli3创建libs库的实现方法
2019/12/04 Javascript
一起来了解一下JavaScript的预编译(小结)
2021/03/01 Javascript
python的描述符(descriptor)、装饰器(property)造成的一个无限递归问题分享
2014/07/09 Python
wxPython中listbox用法实例详解
2015/06/01 Python
Python的GUI框架PySide的安装配置教程
2016/02/16 Python
python开发游戏的前期准备
2019/05/05 Python
Python中的类与类型示例详解
2019/07/10 Python
Django-rest-framework中过滤器的定制实例
2020/04/01 Python
150行Python代码实现带界面的数独游戏
2020/04/04 Python
Python爬虫代理池搭建的方法步骤
2020/09/28 Python
基于CSS3实现的几个小loading效果
2018/09/27 HTML / CSS
html5开发之viewport使用
2013/10/17 HTML / CSS
国际性能运动服装品牌:Dare 2b
2018/07/27 全球购物
美国家用和厨房电器销售网站:Appliances Connection
2020/01/24 全球购物
带薪年假请假条
2014/02/04 职场文书
医院工作检讨书范文
2014/02/10 职场文书
南京导游词
2015/02/03 职场文书