C#脚本引擎之AScript与Jurassic、Jint对比
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
本篇文章从性能、功能、应用场景等方面对比.NET几款JavaScript脚本引擎。 一 简介1.1 AScriptAScript是一个开源的C#动态脚本解析执行引擎,支持自定义语法,支持扩展多种脚本语言(目前已支持C#/Python3/SQL/JavaScript/Lua五种语言),支持解释执行和编译执行两种执行模式,并且支持编译缓存,以适配不同应用场景下对性能的要求。 AScript的JavaScript语言包: 目前官方最新版本:
开源地址: 相关文章: 1.2 JurassicJurassic是一个开源的托管JavaScript执行引擎,支持ECMAScript 3和ECMAScript 5的功能,将JavaScript编译为.NET字节码(CIL),而非解释执行。 目前官方最新版本:3.2.9 开源地址:github.com/paulbartrum/jurassic 1.3 JintJint是一款专为.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);
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);
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 导入模块模块文件路径: 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个参数类型为当前要扩展的对象类型 示例:扩展字符串实例函数, 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()"));
方法名规则: 示例:扩展字符串静态函数, 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')"));
方法名规则: 示例:添加字符串构造函数 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不支持。 需要实现 2.7 嵌入其他语言脚本AScript支持脚本中使用 示例:使用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 四 总结
1)Jint语法支持最全面,性能高; 项目中如何选择? 1)需要全面的js语法支持,可以选择Jint 阅读原文:点击这里 该文章在 2026/9/2 10:12:00 编辑过 |
关键字查询
相关文章
正在查询... |