用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 相关文章推荐
用cssText批量修改样式
Aug 29 Javascript
对 lightbox JS 图片控件进行了一下改造, 使其他支持复杂的图片说明
Mar 20 Javascript
script标签的 charset 属性使用说明
Dec 04 Javascript
基于jQuery的动态表格插件
Mar 28 Javascript
JavaScript中判断变量是数组、函数或是对象类型的方法
Feb 25 Javascript
JavaScript闭包实例详解
Jun 03 Javascript
Vue.js分页组件实现:diVuePagination的使用详解
Jan 10 Javascript
浅析微信扫码登录原理(小结)
Oct 29 Javascript
Vue表单之v-model绑定下拉列表功能
May 14 Javascript
简单了解微信小程序 e.target与e.currentTarget的不同
Sep 27 Javascript
vue页面加载时的进度条功能(实例代码)
Jan 13 Javascript
vue修饰符.capture和.self的区别
Apr 22 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
PHP6新特性分析
2016/03/03 PHP
Symfony2之session与cookie用法小结
2016/03/18 PHP
PHP7实现和CryptoJS的AES加密方式互通示例【AES-128-ECB加密】
2019/06/08 PHP
深入理解PHP+Mysql分布式事务与解决方案
2020/12/03 PHP
JQuery 学习笔记 element属性控制
2009/07/23 Javascript
在JavaScript中使用对数Math.log()方法的教程
2015/06/15 Javascript
js实现用户离开页面前提示是否离开此页面的方法(包括浏览器按钮事件)
2015/07/18 Javascript
基于JS实现的倒计时程序实例
2015/07/24 Javascript
JSON格式的时间/Date(2367828670431)/格式转为正常的年-月-日 格式的代码
2016/07/27 Javascript
Js操作DOM元素及获取浏览器高宽的简单方法
2016/09/08 Javascript
D3.js实现雷达图的方法详解
2016/09/22 Javascript
JavaScript数组去重的6个方法
2017/01/21 Javascript
ES6学习教程之对象的扩展详解
2017/05/02 Javascript
利用three.js画一个3D立体的正方体示例代码
2017/11/19 Javascript
使用express+multer实现node中的图片上传功能
2018/02/02 Javascript
原生JavaScript实现todolist功能
2018/03/02 Javascript
基于JavaScript canvas绘制贝塞尔曲线
2018/12/25 Javascript
vue中h5端打开app(判断是安卓还是苹果)
2021/02/26 Vue.js
[50:11]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第三场
2018/04/09 DOTA
github配置使用指南
2014/11/18 Python
答题辅助python代码实现
2018/01/16 Python
详解python中init方法和随机数方法
2019/03/13 Python
numpy库ndarray多维数组的维度变换方法(reshape、resize、swapaxes、flatten)
2020/04/28 Python
详解Django配置JWT认证方式
2020/05/09 Python
Scrapy爬虫文件批量运行的实现
2020/09/30 Python
Book Depository亚太地区:一家领先的国际图书零售商
2019/05/05 全球购物
全球性的众包图形设计市场:DesignCrowd
2021/02/02 全球购物
武汉高蓝德国际.net机试
2016/06/24 面试题
最新大学职业规划书范文
2013/12/30 职场文书
心得体会范文
2014/01/04 职场文书
广告词串烧
2014/03/19 职场文书
2014年社区工作总结
2014/11/18 职场文书
实习班主任自我评价
2015/03/11 职场文书
社区禁毒宣传活动总结
2015/05/07 职场文书
环保宣传语大全
2015/07/13 职场文书
Golang map映射的用法
2022/04/22 Golang