用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 相关文章推荐
js/jquery获取浏览器窗口可视区域高度和宽度以及滚动条高度实现代码
Dec 17 Javascript
IE下双击checkbox反应延迟问题的解决方法
Mar 27 Javascript
JS创建类和对象的两种不同方式
Aug 08 Javascript
jQuery右下角旋转环状菜单特效代码
Aug 10 Javascript
利用AJAX实现WordPress中的文章列表及评论的分页功能
May 17 Javascript
JS读取XML文件数据并以table形式显示数据的方法(兼容IE与火狐)
Jun 02 Javascript
jQuery插件FusionCharts绘制2D柱状图和折线图的组合图效果示例【附demo源码】
Apr 10 jQuery
vuex与组件联合使用的方法
May 10 Javascript
JavaScript中toLocaleString()和toString()的区别实例分析
Aug 14 Javascript
配置一个vue3.0项目的完整步骤
Apr 26 Javascript
Javascript原型链及instanceof原理详解
May 25 Javascript
ant design的table组件实现全选功能以及自定义分页
Nov 17 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
用header 发送cookie的php代码
2007/03/16 PHP
PHP批量生成缩略图的代码
2008/07/19 PHP
PHP定时执行计划任务的多种方法小结
2011/12/19 PHP
php使用gettimeofday函数返回当前时间并存放在关联数组里
2015/03/19 PHP
input+select(multiple) 实现下拉框输入值
2009/05/21 Javascript
QUnit jQuery的TDD框架
2010/11/04 Javascript
编写可维护面向对象的JavaScript代码[翻译]
2011/02/12 Javascript
js 取时间差去掉周六周日实现代码
2012/12/25 Javascript
Javascript中产生固定结果的函数优化技巧
2013/01/16 Javascript
Javascript模块化编程(三)require.js的用法及功能介绍
2013/01/17 Javascript
利用jquery包将字符串生成二维码图片
2013/09/12 Javascript
jquery ajax 调用失败的原因示例介绍
2013/09/27 Javascript
实现图片首尾平滑轮播(JS原生方法—节流)
2017/10/17 Javascript
Bootstrap模态对话框用法简单示例
2018/08/31 Javascript
vue 本地环境跨域请求proxyTable的方法
2018/09/19 Javascript
vue自定义表单生成器form-create使用详解
2019/07/19 Javascript
JavaScript设计模型Iterator实例解析
2020/01/22 Javascript
Vue 组件复用多次自定义参数操作
2020/07/27 Javascript
[02:51]2014DOTA2 TI小组赛总结中国军团全部进军钥匙球馆
2014/07/15 DOTA
零基础写python爬虫之爬虫框架Scrapy安装配置
2014/11/06 Python
python中的全局变量用法分析
2015/06/09 Python
python学生信息管理系统(初级版)
2018/10/17 Python
python3 实现验证码图片切割的方法
2018/12/07 Python
Python实现计算文件MD5和SHA1的方法示例
2019/06/11 Python
Python 转换文本编码实现解析
2019/08/27 Python
Python如何把Spark数据写入ElasticSearch
2020/04/18 Python
python 解决Fatal error in launcher:错误问题
2020/05/21 Python
Python3安装模块报错Microsoft Visual C++ 14.0 is required的解决方法
2020/07/28 Python
移动端开发HTML5页面点击按钮后出现闪烁或黑色背景的解决办法
2018/09/19 HTML / CSS
伦敦平价潮流珠宝首饰品牌:Astrid & Miyu
2016/10/10 全球购物
亚马逊西班牙购物网站:amazon西班牙
2017/03/06 全球购物
三爱活动实施方案
2014/03/19 职场文书
小学优秀班干部事迹材料
2014/05/25 职场文书
幼儿园开学报名通知
2015/07/16 职场文书
《法国号》教学反思
2016/02/22 职场文书
HTML5之高度塌陷问题的解决
2022/06/01 HTML / CSS