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

浙公网安备 33010602011771号