C#脚本引擎之AScript与Jurassic、Jint对比

本篇文章从性能、功能、应用场景等方面对比.NET几款JavaScript脚本引擎。

一 简介

1.1 AScript

AScript是一个开源的C#动态脚本解析执行引擎,支持自定义语法,支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),支持解释执行和编译执行两种执行模式,并且支持编译缓存,以适配不同应用场景下对性能的要求。

AScript的JavaScript语言包:AScript.Lang.JavaScript

目前官方最新版本:

  • AScript:1.7.0
  • AScript.Lang.JavaScript:0.2.0

开源地址:

相关文章:

1.2 Jurassic

Jurassic是一个开源的托管JavaScript执行引擎,支持ECMAScript 3和ECMAScript 5的功能,将JavaScript编译为.NET字节码(CIL),而非解释执行。

目前官方最新版本:3.2.9

开源地址:github.com/paulbartrum/jurassic

1.3 Jint

Jint是一款专为.NET平台设计的JavaScript解释器,支持.NET Standard 2.0和.NET 4.6.2及以上版本。它能够在任何现代.NET平台上运行,为开发者提供了一个安全沙箱环境,以便在.NET应用程序中运行JavaScript代码。Jint不仅支持将原生.NET对象和函数暴露给JavaScript代码,还支持脚本化,允许用户使用JavaScript自定义应用程序。

目前官方最新版本:4.16.1

开源地址:github.com/sebastienros/jint

二 功能

由于AScript默认脚本语言为C#,需要安装JavaScript语言包,并注册JavaScript语言:

复制代码
Script.Langs.Set("js", AScript.Lang.JavaScript.JavaScriptLang.Instance);

方式一:在ScriptContext上下文中指定js语言

复制代码
1 var script = new AScript.Script();
2 script.Context.Langs = new[] { "js" };
3 var result = script.Eval(xxx);

方式二:在脚本中指定js语言

复制代码
1 string code = @"
2 @lang js
3 function sum(a, b) {
4     return a+b;
5 }
6 ";
7 var script = new AScript.Script();
8 var result = script.Eval(code);

方式三:全局设置js为默认语言

复制代码
Script.Langs.SetDefault("js");

建议使用 方式一 或者 方式二来执行js脚本。

2.1 解释执行

1)AScript

默认为解释执行模式:

复制代码
1 var script = new AScript.Script();
2 script.Context.Langs = new[] { "js" };
3 var result = script.Eval("5 + 6");

2)Jurassic

不支持。

3)Jint

复制代码
1 var engine = new Jint.Engine();
2 var result = engine.Evaluate("5 + 6");

2.2 编译执行

将脚本编译生成委托,再执行;编译生成委托的成本较大,但是编译后执行快,因此如果能缓存编译生成的委托,整体性能就比较高。

1)AScript

  • 编译执行
复制代码
1 var script = new AScript.Script();
2 // 可以统一设置为编译执行模式
3 // script.Options.CompileMode = AScript.ECompileMode.All;
4 script.Context.Langs = new[] { "js" };
5 // 或者Eval方法指定为编译执行模式
6 var result = script.Eval("5 + 6", AScript.ECompileMode.All);
  • 编译缓存
复制代码
1 var script = new AScript.Script();
2 script.Context.Langs = new[] { "js" };
3 var result = script.Eval("5 + 6", -1);

Eval方法定义如下:

复制代码
 1 /// <summary>
 2 /// 计算表达式,返回结果
 3 /// </summary>
 4 /// <param name="expression">脚本</param>
 5 /// <param name="cacheTime">
 6 /// <para>缓存时长</para>
 7 /// <para>为0表示不使用缓存(默认);</para>
 8 /// <para>-1表示永久缓存;</para>
 9 /// <para>大于0表示缓存时长(单位:毫秒)</para>
10 /// </param>
11 /// <param name="cacheKey">
12 /// 缓存key(如果为空则取表达式字符串)
13 /// </param>
14 /// <param name="cacheVersion">
15 /// 缓存版本号,如果版本号有变化,则重新编译并缓存
16 /// </param>
17 /// <returns></returns>
18 public object Eval(string expression, int cacheTime = 0, string cacheKey = null, string cacheVersion = null);
  • 生成Lambda表达式树
复制代码
1 var script = new AScript.Script();
2 script.Context.Langs = new[] { "js" };
3 Expression<Func<int, bool>> lambda = script.Lambda<int, bool>("a>0 && a<10", "a");

生成结果为:

复制代码
.Lambda #Lambda1<System.Func`2[System.Int32,System.Boolean]>(System.Int32 $a) {
    $a > 0 && $a < 10
}

使用场景:将脚本转为C#表达式树,可在LINQ中直接使用,比如LINQ to Sql的Where条件查询。

  • 编译生成委托
复制代码
1 var script = new AScript.Script();
2 script.Context.Langs = new[] { "js" };
3 Func<int> func = script.Compile<int>("5 + 6");
4 var result = func();

使用场景:将脚本转为C#委托,项目代码中调用委托来实现动态脚本注入功能,或者在LINQ to Object中使用。

2)Jurassic

仅支持编译执行,不支持生成委托:

复制代码
1 var engine = new Jurassic.ScriptEngine();
2 var result = engine.Evaluate("5 + 6");

3)Jint

不支持。

2.3 导入模块

模块文件路径:./modules/mymodule.js

复制代码
 1 var total = 1;
 2 
 3 export function sum(a, b) {
 4     total += 1;
 5     return a + b;
 6 }
 7 
 8 export function fib(a) {
 9     if (a <= 1) return a;
10     return fib(a - 1) + fib(a - 2);
11 }
12 
13 export function getTotal() {
14     return total;
15 }
16 
17 /*
18 AScript兼容CommonJS写法,import/require互相兼容
19 module.exports = { sum, fib }
20 */
21 export default { sum, fib }

1)AScript

  • 设置模块目录
复制代码
1 // 全局添加模块目录
2 JavaScriptLang.Instance.Modules.AddDir("./modules");
3 // 或者当前上下文添加模块目录,只在当前范围有效,不影响全局
4 var script = new AScript.Script();
5 script.Context.Langs = new[] { "js" };
6 script.Context.Modules.AddDir("./modules");
  • 脚本中导入模块
复制代码
 1 var code = @"
 2 import m, { getTotal } from 'mymodule';
 3 // 兼容require
 4 // var m = require('mymodule');
 5 m.sum(1, 2) + m.fib(9);
 6 ";
 7 var script = new Script();
 8 script.Context.Langs = new[] { "js" };
 9 Assert.AreEqual(37L, script.Eval(code));
10 Assert.AreEqual(2L, script.Eval("getTotal()"));
11 Assert.AreEqual(30L, script.Eval("m.sum(10, 20)"));
12 Assert.AreEqual(3L, script.Eval("getTotal()"));
  • 添加自定义模块
复制代码
 1 var code = @"
 2 import m, { add, mult } from 'utils';
 3 add(1, 2) + mult(5, m.x)
 4 ";
 5 var script = new Script();
 6 script.Context.Langs = new[] { "js" };
 7 script.Context.Modules.Add("utils", builder =>
 8 {
 9     builder.OnInstall(context =>
10     {
11         var export = new JavaScriptExportModule();
12         export.Export("x", 8);
13         export.Export<Func<int, int, int>>("add", (a, b) => a + b);
14         export.Export<Func<int, int, int>>("mult", (a, b) => a * b);
15         export.ExportDefaultByNamed("x", "add", "mult");
16         return export;
17     });
18 });
19 Assert.AreEqual(43, script.Eval(code));

2)Jurassic

不支持

3)Jint

官方说明支持import/export模块,查了官方手册和网上资料没跑成功。

2.4 异步

1)AScript

复制代码
1 var script = new Script();
2 script.Context.Langs = new[] { "js" };
3 // 宿主异步,脚本异步
4 var result = await script.EvalAsync("await new Promise((resolve, reject) => resolve(2)).then(x => x * 3)");
5 // 或者宿主同步,脚本异步
6 // result = script.Eval("await new Promise((resolve, reject) => resolve(2)).then(x => x * 3)");
7 Assert.AreEqual(6L, result);

2)Jurassic

不支持

3)Jint

复制代码
1 var engine = new Jint.Engine();
2 // 脚本中不要写await
3 var result = await engine.EvaluateAsync("new Promise((resolve, reject) => resolve(42)).then(x => 2 * x)");
4 Console.WriteLine(result);

2.5 扩展函数

AScript支持,Jurassic/Jint不支持。

扩展脚本语言通常会使用扩展函数、属性、构造函数等方式来支持该脚本语言的语法特性。

  • 扩展实例函数

正常添加函数,第1个参数类型为当前要扩展的对象类型

示例:扩展字符串实例函数,toDate转时间。

复制代码
1 var script = new Script();
2 script.Context.Langs = new[] { "js" };
3 script.Context.AddFunc<string, DateTime>("toDate", s => DateTime.Parse(s));
4 Assert.AreEqual(new DateTime(2026, 8, 26), script.Eval("'2026-08-26'.toDate()"));
  • 扩展静态函数

方法名规则:{ClassName}_{扩展方法名}

示例:扩展字符串静态函数,String.toDate转时间

复制代码
1 var script = new Script();
2 script.Context.Langs = new[] { "js" };
3 script.Context.AddFunc<string, DateTime>("String_toDate", s => DateTime.Parse(s));
4 Assert.AreEqual(new DateTime(2026, 8, 26), script.Eval("String.toDate('2026-08-26')"));
  • 扩展构造函数

方法名规则:new_{ClassName}

示例:添加字符串构造函数String(Date)

复制代码
1 var script = new Script();
2 script.Context.Langs = new[] { "js" };
3 script.Context.AddFunc<DateTime, string>("new_String", time => time.ToString("yyyy-MM-dd"));
4 Assert.AreEqual(DateTime.Now.ToString("yyyy-MM-dd"), script.Eval("new String(new Date())"));

2.6 自定义语法

AScript支持,Jurassic/Jint不支持。

需要实现ITokenHandler接口,稍微复杂一些,扩展脚本语言也是通过该接口来实现语法特性的,可参考相关文章:

2.7 嵌入其他语言脚本

AScript支持脚本中使用@lang/@end嵌入其他语言的脚本。

示例:使用C#语言的LINQ语法过滤js数组

复制代码
 1 var code = @"
 2 var arr = [{name:'tom', age:18}, {name:'jim', age:23}, {name:'john', age:16}];
 3 @lang csharp
 4 var arr2 = arr.Where(a=>a.age<20).ToList();
 5 @end
 6 arr2.length
 7 ";
 8 var script = new Script();
 9 script.Context.Langs = new[] { "js" };
10 Assert.AreEqual(2L, script.Eval(code));

三 性能测试

使用Benchmark工具进行性能基准测试,测试环境:

复制代码
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22621.4317/22H2/2022Update/SunValley2)
AMD Ryzen 7 7840H with Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.316
  [Host]     : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.36 (6.0.3624.51421), X64 RyuJIT AVX2

安装nuget包:

复制代码
install-package AScript
install-package AScript.Lang.JavaScript
install-package Jurassic
install-package Jint

3.1 常量计算

复制代码
 1 // 待测试表达式
 2 private static readonly string s = "100 * (5 + 5) * (6-2)";
 3 // 用于测试结果比较,确保测试执行结果是正确的
 4 private static readonly int r = 100 * (5 + 5) * (6 - 2);
 5 
 6 static JavaScriptTest01_const()
 7 {
 8     // 注册js语言,并全局设置为默认语言
 9     Script.Langs.Set("js", JavaScriptLang.Instance, setDefault: true);
10 }

1)解释执行

复制代码
 1 [Benchmark]
 2 public void AScript1()
 3 {
 4     var script = new Script();
 5     var result = script.Eval<int>(s);
 6     if (result != r) throw new Exception("result error");
 7 }
 8 
 9 [Benchmark]
10 public void Jint1()
11 {
12     var engine = new Jint.Engine();
13     var result = engine.Evaluate(s);
14     if (result != r) throw new Exception("result error");
15 }

2)编译执行

复制代码
 1 [Benchmark]
 2 public void AScript2_Compile()
 3 {
 4     var script = new Script();
 5     var result = script.Eval<int>(s, ECompileMode.All);
 6     if (result != r) throw new Exception("result error");
 7 }
 8 
 9 [Benchmark]
10 public void Jurassic2()
11 {
12     var engine = new Jurassic.ScriptEngine();
13     var result = engine.Evaluate<int>(s);
14     if (result != r) throw new Exception("result error");
15 }

3)编译缓存

复制代码
1 [Benchmark]
2 public void AScript3_Cache()
3 {
4     var script = new Script();
5     var result = script.Eval<int>(s, -1);
6     if (result != r) throw new Exception("result error");
7 }

测试结果如下:

性能:Jint > AScript > Jurassic

3.2 定义变量

复制代码
1 private static readonly string s = "var a = 100; var b = 5; var c = 6; a * (b + 5) * (c-2)";
2 private static readonly int r = 100 * (5 + 5) * (6 - 2);

测试结果:

性能:Jint > AScript > Jurassic

3.3 注入变量

复制代码
1 private static readonly string s = "a * (b + 5) * (c-2)";
2 private static readonly int r = 100 * (5 + 5) * (6 - 2);

1)解释执行

复制代码
 1 [Benchmark]
 2 public void AScript1()
 3 {
 4     var script = new Script();
 5     script.Context.SetVar("a", 100);
 6     script.Context.SetVar("b", 5);
 7     script.Context.SetVar("c", 6);
 8     var result = script.Eval<int>(s);
 9     if (result != r) throw new Exception("result error");
10 }
11 
12 [Benchmark]
13 public void Jint1()
14 {
15     var engine = new Jint.Engine();
16     engine.SetValue("a", 100);
17     engine.SetValue("b", 5);
18     engine.SetValue("c", 6);
19     var result = engine.Evaluate(s);
20     if (result != r) throw new Exception("result error");
21 }

2)编译执行

复制代码
 1 [Benchmark]
 2 public void AScript2_Compile()
 3 {
 4     var script = new Script();
 5     script.Context.SetVar("a", 100);
 6     script.Context.SetVar("b", 5);
 7     script.Context.SetVar("c", 6);
 8     var result = script.Eval<int>(s, ECompileMode.All);
 9     if (result != r) throw new Exception("result error");
10 }
11 
12 [Benchmark]
13 public void Jurassic2()
14 {
15     var engine = new Jurassic.ScriptEngine();
16     engine.SetGlobalValue("a", 100);
17     engine.SetGlobalValue("b", 5);
18     engine.SetGlobalValue("c", 6);
19     var result = engine.Evaluate<int>(s);
20     if (result != r) throw new Exception("result error");
21 }

3)编译缓存

复制代码
 1 [Benchmark]
 2 public void AScript3_Cache()
 3 {
 4     var script = new Script();
 5     script.Context.SetVar("a", 100);
 6     script.Context.SetVar("b", 5);
 7     script.Context.SetVar("c", 6);
 8     var result = script.Eval<int>(s, -1);
 9     if (result != r) throw new Exception("result error");
10 }

测试结果:

性能:Jint > AScript > Jurassic

3.4 注入函数

复制代码
1 private static readonly string s = "sum(5, 6)";
2 private static readonly int r = 11;

1)解释执行

复制代码
 1 [Benchmark]
 2 public void AScript1()
 3 {
 4     var script = new Script();
 5     script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b);
 6     var result = script.Eval<int>(s);
 7     if (result != r) throw new Exception("result error");
 8 }
 9 
10 [Benchmark]
11 public void Jint1()
12 {
13     var engine = new Jint.Engine();
14     engine.SetValue("sum", new Func<int, int, int>((a, b) => a + b));
15     var result = engine.Evaluate(s);
16     if (result != r) throw new Exception("result error");
17 }

2)编译执行

复制代码
 1 [Benchmark]
 2 public void AScript2_Compile()
 3 {
 4     var script = new Script();
 5     script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b);
 6     var result = script.Eval<int>(s, ECompileMode.All);
 7     if (result != r) throw new Exception("result error");
 8 }
 9 
10 [Benchmark]
11 public void Jurassic2()
12 {
13     var engine = new Jurassic.ScriptEngine();
14     engine.SetGlobalFunction("sum", new Func<int, int, int>((a, b) => a + b));
15     var result = engine.Evaluate<int>(s);
16     if (result != r) throw new Exception("result error");
17 }

3)编译缓存

复制代码
1 [Benchmark]
2 public void AScript3_Cache()
3 {
4     var script = new Script();
5     script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b);
6     var result = script.Eval<int>(s, -1);
7     if (result != r) throw new Exception("result error");
8 }

测试结果:

性能:AScript > Jint > Jurassic

3.5 执行文件

utils.js文件:

复制代码
 1 function sum(a, b) {
 2     return a + b;
 3 }
 4 
 5 function factorial(n) {
 6     if (n <= 1) {
 7         return 1;
 8     }
 9     return n * factorial(n - 1);
10 }

测试脚本:

复制代码
1 private static readonly string filePath = "./utils.js";
2 private static readonly string s = "sum(factorial(5), 10)";
3 private static readonly int r = 5 * 4 * 3 * 2 * 1 + 10;

1)解释执行

复制代码
 1 [Benchmark]
 2 public void AScript1()
 3 {
 4     var script = new Script();
 5     script.EvalFile(filePath);
 6     var result = script.Eval<int>(s);
 7     if (result != r) throw new Exception("result error");
 8 }
 9 
10 [Benchmark]
11 public void Jint1()
12 {
13     var engine = new Jint.Engine();
14     engine.Evaluate(System.IO.File.ReadAllText(filePath));
15     var result = engine.Evaluate(s);
16     if (result != r) throw new Exception("result error");
17 }

2)编译执行

复制代码
 1 [Benchmark]
 2 public void AScript2_Compile()
 3 {
 4     var script = new Script();
 5     script.EvalFile(filePath, ECompileMode.All);
 6     var result = script.Eval<int>(s, ECompileMode.All);
 7     if (result != r) throw new Exception("result error");
 8 }
 9 
10 [Benchmark]
11 public void Jurassic2()
12 {
13     var engine = new Jurassic.ScriptEngine();
14     engine.ExecuteFile(filePath);
15     var result = engine.Evaluate<int>(s);
16     if (result != r) throw new Exception("result error");
17 }

3)编译缓存

复制代码
1 [Benchmark]
2 public void AScript3_Cache()
3 {
4     var script = new Script();
5     script.EvalFile(filePath, -1);
6     var result = script.Eval<int>(s, -1);
7     if (result != r) throw new Exception("result error");
8 }

测试结果:

性能:AScript > Jint > Jurassic

3.6 对象

复制代码
1 private static readonly string s = @"
2 var obj = { name: 'Alice', address: { city: 'Beijing' } };
3 obj.address.city
4 ";
5 private static readonly string r = "Beijing";

测试结果:

性能:Jint > AScript > Jurassic

3.7 for循环

复制代码
1 private static readonly string s = @"
2 var n = 0;
3 for (var i = 0; i < 10000; i++) {
4     n += i;
5 }
6 n
7 ";
8 private static readonly int r = 9999 * 5000;

AScript支持单独编译循环语句。

复制代码
 1 [Benchmark]
 2 public void AScript1()
 3 {
 4     var script = new Script();
 5     var result = script.Eval<int>(s);
 6     if (result != r) throw new Exception("result error");
 7 }
 8 
 9 [Benchmark]
10 public void AScript1_CompileLoop()
11 {
12     var script = new Script();
13     // 仅编译循环语句
14     script.Options.CompileMode = ECompileMode.Loop;
15     var result = script.Eval<int>(s);
16     if (result != r) throw new Exception("result error");
17 }
18 
19 [Benchmark]
20 public void Jint1()
21 {
22     var engine = new Jint.Engine();
23     var result = engine.Evaluate(s);
24     if (result != r) throw new Exception("result error");
25 }

测试结果:

性能:Jint > AScript > Jurassic

脚本中有大循环语句时,AScript解释执行模式性能低,编译执行模式性能高。

3.8 数组

复制代码
1 private static readonly string s = "var arr = [1, 2, 3, 4, 5]; arr[0]+arr[1]+arr[2]+arr[3]+arr[4]";
2 private static readonly int r = 15;

测试结果:

性能:Jint > AScript > Jurassic

3.9 数组(过滤)

Jurassic不支持

复制代码
1 private static readonly string s = "var arr = [1, 2, 3, 4, 5].filter(x => x % 2 == 0); arr.length";
2 private static readonly int r = 2;

测试结果:

性能:Jint > AScript

四 总结

功能 AScript Jurassic Jint
.NET框架 net45 netstandard2.0 netstandard2.1 net6.0 net8.0 netstandard2.0 net46 netstandard2.0 netstandard2.1 net8.0 net10.0
性能
ES5语法 支持 支持 支持
ES6语法 部分 部分 支持
ES2016~2021新特性 部分 少量 支持
ES2022~2025新特性 不支持 不支持 支持
解释执行 支持 不支持 支持
编译执行 支持 支持 不支持
编译缓存 支持 不支持 不支持
解构语法 支持 不支持 支持
模块功能 支持 不支持 ?
异步 支持 不支持 支持
自定义语法 支持 不支持 不支持
扩展函数 支持 不支持 不支持

1)Jint语法支持最全面,性能高;

2)AScript应用场景支持最全面,JavaScript语法支持也较多,性能高(解释执行大循环语句性能低,可开启编译循环语句或者改用编译执行模式来提高性能);

3)Jurassic性能中等,很多新语法不支持,比如:()=>{}var [name,age]=['tom',25]'5'.padStart(4, '0')等。

项目中如何选择?

1)需要全面的js语法支持,可以选择Jint

2)考虑各种应用场景,以及功能扩展,可以选择AScript

相关推荐
lzhdim2 小时前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
惊鸿醉4 小时前
Unity 实战:用讯飞 WebAPI 做一个“说普通话、播方言“的语音程序
unity·c#·游戏引擎·语音识别
略略略咯咯5 小时前
C#Unity
开发语言·unity·c#
kyriewen6 小时前
我踩了3次同一个坑才明白:JavaScript里比0.1+0.2更隐蔽的5个数字陷阱
前端·javascript·面试
zhifou1234567 小时前
Vue基础(二)
前端·javascript·vue.js
Behavior7 小时前
Unity文字显示带竖线问题
c#·unity3d
默_笙7 小时前
🙃 我让爬虫终于看到了我的网站,后端同事说"这也行?"(下):App Router 全栈实战
前端·javascript
九九落7 小时前
JavaScript 实现北京时间精确到毫秒显示:UTC+8、Asia/Shanghai 与在线时间校准详解
开发语言·javascript·ecmascript
FEF前端团队8 小时前
小程序微信支付 V3 接入实战手册:从商户配置到前后端落地
javascript·后端·node.js