本篇文章从性能、功能、应用场景等方面对比.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