用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 相关文章推荐
一段效率很高的for循环语句使用方法
Aug 13 Javascript
js树形控件脚本代码
Jul 24 Javascript
JQuery SELECT单选模拟jQuery.select.js
Nov 12 Javascript
javascript点击按钮实现隐藏显示切换效果
Feb 03 Javascript
Bootstrap3制作图片轮播效果
May 12 Javascript
jQuery中JSONP的两种实现方式详解
Sep 26 Javascript
js 定位到某个锚点的方法
Nov 19 Javascript
使用gulp搭建本地服务器并实现模拟ajax
Apr 05 Javascript
JavaScript严格模式下关于this的几种指向详解
Jul 12 Javascript
JavaScript表单即时验证 验证不成功不能提交
Aug 31 Javascript
Bootstrap框架建立树形菜单(Tree)的实例代码
Oct 30 Javascript
关于vue表单提交防双/多击的例子
Oct 31 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实现图形显示Ip地址的代码及注释
2014/01/20 PHP
PHP中ini_set和ini_get函数的用法小结
2014/02/18 PHP
针对多用户实现头像上传功能PHP代码 适用于登陆页面制作
2016/08/17 PHP
jQuery 中使用JSON的实现代码
2011/12/01 Javascript
NodeJS的模块写法入门(实例代码)
2012/03/07 NodeJs
js去除重复字符串两种实现方法
2013/01/09 Javascript
jquery showModelDialog的使用方法示例详解
2013/11/19 Javascript
javascript和jquery修改a标签的href属性
2013/12/16 Javascript
无刷新上传文件并返回自定义值
2015/06/11 Javascript
在JavaScript应用中实现延迟加载的方法
2015/06/25 Javascript
jQuery.prop() 使用详解
2015/07/19 Javascript
深入理解angularjs过滤器
2016/05/25 Javascript
javascript 中的事件委托详解
2016/10/25 Javascript
使用JavaScript实现一个小程序之99乘法表
2017/09/21 Javascript
jQuery序列化form表单数据为JSON对象的实现方法
2018/09/20 jQuery
python切换hosts文件代码示例
2013/12/31 Python
Python计算三维矢量幅度的方法
2015/06/15 Python
Python聚类算法之凝聚层次聚类实例分析
2015/11/20 Python
MySQL中表的复制以及大型数据表的备份教程
2015/11/25 Python
python设计模式大全
2016/06/27 Python
Scrapy抓取京东商品、豆瓣电影及代码分享
2017/11/23 Python
用python的requests第三方模块抓取王者荣耀所有英雄的皮肤实例
2017/12/14 Python
微信跳一跳python自动代码解读1.0
2018/01/12 Python
Python中optparser库用法实例详解
2018/01/26 Python
python读取中文txt文本的方法
2018/04/12 Python
使用python opencv对目录下图片进行去重的方法
2019/01/12 Python
DataFrame:通过SparkSql将scala类转为DataFrame的方法
2019/01/29 Python
Python使用pandas和xlsxwriter读写xlsx文件的方法示例
2019/04/09 Python
关于Python形参打包与解包小技巧分享
2019/08/24 Python
python使用PIL和matplotlib获取图片像素点并合并解析
2019/09/10 Python
使用python处理题库表格并转化为word形式的实现
2020/04/14 Python
印度尼西亚综合购物网站:Lazada印尼
2016/09/07 全球购物
Topshop美国官网:英国快速时尚品牌
2019/05/16 全球购物
银行服务明星推荐材料
2014/05/29 职场文书
交通事故起诉书
2015/05/19 职场文书
新员工入职感想
2015/08/07 职场文书