Project Icon

jint

跨平台高性能 JavaScript 解释器

Jint 是一款专为 .NET 平台开发的 JavaScript 解释器,兼容 .NET Standard 2.0 和 .NET 4.6.2 及更高版本。它提供安全的沙盒环境执行 JavaScript 代码,支持访问原生 .NET 对象和函数,实现脚本化功能。Jint 广泛支持 ECMAScript 标准,覆盖 ES6 到 ES2024 的众多特性,并提供执行限制和 .NET 互操作能力。

构建 NuGet NuGet MyGet 加入Gitter聊天

Jint

Jint是一个适用于.NET的JavaScript解释器,支持.NET Standard 2.0和.NET 4.6.2(及更高版本)目标,可以在任何现代.NET平台上运行。

使用场景和用户

  • 在您的.NET应用程序中安全沙箱环境中运行JavaScript
  • 将原生.NET对象和函数暴露给您的JavaScript代码(获取数据库查询结果为JSON,调用.NET方法等)
  • 支持在.NET应用程序中进行脚本编写,允许用户使用JavaScript自定义您的应用程序(如Unity游戏)

Jint的一些用户包括 RavenDBEventStoreOrchardCoreELSA WorkflowsdocfxJavaScript Engine Switcher 等等。

支持的功能

ECMAScript 2015 (ES6)

  • ✔ ArrayBuffer
  • ✔ 箭头函数表达式
  • ✔ 二进制和八进制字面量
  • ✔ 类支持
  • ✔ DataView
  • ✔ 解构
  • ✔ 默认参数、rest参数和展开运算符
  • ✔ 增强的对象字面量
  • for...of
  • ❌ 生成器
  • ✔ 模板字符串
  • ✔ 变量的词法作用域(let和const)
  • ✔ Map和Set
  • ✔ 模块和模块加载器
  • ✔ Promise(实验性,API不稳定)
  • ✔ Reflect
  • ✔ 代理
  • ✔ Symbol
  • ❌ 尾调用
  • ✔ 类型化数组
  • ✔ Unicode
  • ✔ Weakmap和Weakset

ECMAScript 2016

  • Array.prototype.includes
  • awaitasync
  • ✔ 变量和函数的块级作用域
  • ✔ 指数运算符 **
  • ✔ 解构模式(变量)

ECMAScript 2017

  • Object.valuesObject.entriesObject.getOwnPropertyDescriptors
  • ❌ 共享内存和原子操作

ECMAScript 2018

  • Promise.prototype.finally
  • ✔ 正则表达式命名捕获组
  • ✔ 对象字面量的rest/spread运算符(...identifier
  • ✔ SharedArrayBuffer

ECMAScript 2019

  • Array.prototype.flatArray.prototype.flatMap
  • String.prototype.trimStartString.prototype.trimEnd
  • Object.fromEntries
  • Symbol.description
  • ✔ 可选的catch绑定

ECMAScript 2020

  • BigInt
  • export * as ns from
  • for-in增强
  • globalThis对象
  • import
  • import.meta
  • ✔ 空值合并运算符(??
  • ✔ 可选链
  • Promise.allSettled
  • String.prototype.matchAll

ECMAScript 2021

  • ✔ 逻辑赋值运算符(&&= ||= ??=
  • ✔ 数字分隔符(1_000
  • AggregateError
  • Promise.any
  • String.prototype.replaceAll
  • WeakRef
  • FinalizationRegistry

ECMAScript 2022

  • ✔ 类字段
  • ✔ 正则表达式匹配索引
  • ✔ 顶层await
  • ✔ 私有字段的人体工程学品牌检查
  • .at()
  • ✔ 可访问的Object.prototype.hasOwnPropertyObject.hasOwn
  • ✔ 类静态块
  • ✔ 错误原因

ECMAScript 2023

  • ✔ 数组从末尾查找
  • ✔ 通过复制更改数组
  • ✔ Hashbang语法
  • ✔ Symbol作为WeakMap键

ECMAScript 2024

  • ✔ ArrayBuffer增强 - ArrayBuffer.prototype.resizeArrayBuffer.prototype.transfer
  • Atomics.waitAsync
  • ✔ 确保字符串格式正确 - String.prototype.ensureWellFormedString.prototype.isWellFormed
  • ✔ 同步可迭代对象分组 - Object.groupByMap.groupBy
  • Promise.withResolvers
  • ❌ 正则表达式标志 /v

ECMAScript Stage 3(尚未确定版本)

  • ✔ Float16Array(需要NET 6或更高版本)
  • ✔ Import属性
  • ✔ JSON模块
  • Promise.try
  • ✔ Set方法(intersectionuniondifferencesymmetricDifferenceisSubsetOfisSupersetOfisDisjointFrom
  • ✔ ShadowRealm
  • ✔ Uint8Array与base64互转

其他

  • 进一步完善的.NET CLR互操作能力
  • 执行约束(递归、内存使用、持续时间)

性能

  • 由于Jint既不生成任何.NET字节码也不使用DLR,它可以非常快速地运行相对较小的脚本
  • 如果您重复运行相同的脚本,应该缓存由Esprima生成的ScriptModule实例,而不是内容字符串
  • 您应该优先在严格模式下运行引擎,这可以提高性能

您可以查看引擎比较结果,请记住每个用例都不同,基准测试可能无法反映您的实际使用情况。

讨论

加入Gitter聊天,或在stackoverflow上使用jint标签提问。

视频

这里有一个简短的视频,展示了Jint的工作原理和一些示例用法

https://docs.microsoft.com/shows/code-conversations/sebastien-ros-on-jint-javascript-interpreter-net

线程安全

引擎实例不是线程安全的,不应同时从多个线程访问它们。

示例

这个例子定义了一个名为log的新值,指向Console.WriteLine,然后运行一个调用log('Hello World!')的脚本。

var engine = new Engine()
    .SetValue("log", new Action<object>(Console.WriteLine));
    
engine.Execute(@"
    function hello() { 
        log('Hello World');
    };
 
    hello();
");

在这里,变量x被设置为3,并在JavaScript中计算x * x。结果直接返回给.NET,在这种情况下作为double9

var square = new Engine()
    .SetValue("x", 3) // 定义一个新变量
    .Evaluate("x * x") // 计算一个语句
    .ToObject(); // 将值转换为.NET对象

您还可以直接传递POCO或匿名对象,并从JavaScript中使用它们。例如,在这个例子中,一个新的Person实例从JavaScript中被操作。

var p = new Person {
    Name = "Mickey Mouse"
};

var engine = new Engine()
    .SetValue("p", p)
    .Execute("p.Name = 'Minnie'");

Assert.AreEqual("Minnie", p.Name);

您可以调用JavaScript函数引用

var result = new Engine()
    .Execute("function add(a, b) { return a + b; }")
    .Invoke("add",1, 2); // -> 3

或直接通过名称调用

var engine = new Engine()
   .Execute("function add(a, b) { return a + b; }");

engine.Invoke("add", 1, 2); // -> 3

访问.NET程序集和类

您可以通过配置引擎实例来允许引擎访问任何.NET类,如下所示:

var engine = new Engine(cfg => cfg.AllowClr());

然后,您可以将System命名空间作为全局值访问。以下是在命令行实用程序上下文中使用它的方式:

jint> var file = new System.IO.StreamWriter('log.txt');
jint> file.WriteLine('Hello World !');
jint> file.Dispose();

甚至可以为常用的.NET方法创建快捷方式

jint> var log = System.Console.WriteLine;
jint> log('Hello World !');
=> "Hello World !"

当允许CLR时,您可以选择性地传递自定义程序集以加载类型。

var engine = new Engine(cfg => cfg
    .AllowClr(typeof(Bar).Assembly)
);

然后,要以与System相同的方式分配本地命名空间,请使用importNamespace

jint> var Foo = importNamespace('Foo');
jint> var bar = new Foo.Bar();
jint> log(bar.ToString());

添加特定CLR类型引用可以这样做

engine.SetValue("TheType", TypeReference.CreateTypeReference<TheType>(engine));

并以这种方式使用

jint> var o = new TheType();

也支持泛型类型。以下是如何声明、实例化和使用List<string>

jint> var ListOfString = System.Collections.Generic.List(System.String);
jint> var list = new ListOfString();
jint> list.Add('foo');
jint> list.Add(1); // 自动转换为String
jint> list.Count; // 2

国际化

如果您不想使用计算机的默认值,可以强制引擎在使用本地JavaScript方法时使用特定的时区或文化。

这个例子强制时区为太平洋标准时间。

var PST = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var engine = new Engine(cfg => cfg.LocalTimeZone(PST));
    
engine.Execute("new Date().toString()"); // Wed Dec 31 1969 16:00:00 GMT-08:00

本例使用法语作为默认区域性。

var FR = CultureInfo.GetCultureInfo("fr-FR");
var engine = new Engine(cfg => cfg.Culture(FR));
    
engine.Execute("new Number(1.23).toString()"); // 1.23
engine.Execute("new Number(1.23).toLocaleString()"); // 1,23

执行约束

执行约束用于在脚本执行期间确保满足资源消耗方面的要求,例如:

  • 脚本不应使用超过 X 内存。
  • 脚本应该只运行最长时间。

你可以通过以下选项配置它们:

var engine = new Engine(options => {

    // 将内存分配限制为 4 MB
    options.LimitMemory(4_000_000);

    // 设置 4 秒的超时。
    options.TimeoutInterval(TimeSpan.FromSeconds(4));

    // 设置执行语句的限制为 1000 条。
    options.MaxStatements(1000);

    // 使用取消令牌。
    options.CancellationToken(cancellationToken);
}

你也可以通过派生 Constraint 基类来编写自定义约束:

public abstract class Constraint
{
    /// 在脚本运行之前调用,当你使用一个引擎对象进行多次执行时非常有用。
    public abstract void Reset();

    // 在每个语句之前调用,以检查是否满足你的要求;如果不满足 - 抛出异常。
    public abstract void Check();
}

例如,我们可以编写一个约束,当 CPU 使用率过高时停止脚本:

class MyCPUConstraint : Constraint
{
    public override void Reset()
    {
    }

    public override void Check()
    {
        var cpuUsage = GetCPUUsage();

        if (cpuUsage > 0.8) // 80%
        {
            throw new OperationCancelledException();
        }
    }
}

var engine = new Engine(options =>
{
    options.Constraint(new MyCPUConstraint());
});

当你重复使用引擎并想要使用取消令牌时,你必须在每次调用 Execute 之前重置令牌:

var engine = new Engine(options =>
{
    options.CancellationToken(new CancellationToken(true));
});

var constraint = engine.Constraints.Find<CancellationConstraint>();

for (var i = 0; i < 10; i++) 
{
    using (var tcs = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
    {
        constraint.Reset(tcs.Token);

        engine.SetValue("a", 1);
        engine.Execute("a++");
    }
}

使用模块

你可以使用模块从多个脚本文件中 importexport 变量:

var engine = new Engine(options =>
{
    options.EnableModules(@"C:\Scripts");
})

var ns = engine.Modules.Import("./my-module.js");

var value = ns.Get("value").AsString();

默认情况下,模块解析算法将被限制在 EnableModules 中指定的基本路径内,并且没有包支持。但是你可以通过两种方式提供自己的包。

使用 JavaScript 源代码定义模块:

engine.Modules.Add("user", "export const name = 'John';");

var ns = engine.Modules.Import("user");

var name = ns.Get("name").AsString();

使用模块构建器定义模块,这允许你从 .NET 导出 CLR 类和值:

// 创建包含 MyClass 类和 version 变量的 'lib' 模块
engine.Modules.Add("lib", builder => builder
    .ExportType<MyClass>()
    .ExportValue("version", 15)
);

// 创建一个用户定义的模块并使用 'lib'
engine.Modules.Add("custom", @"
    import { MyClass, version } from 'lib';
    const x = new MyClass();
    export const result as x.doSomething();
");

// 导入用户定义的模块;这将执行导入链
var ns = engine.Modules.Import("custom");

// 结果包含对模块的"实时"绑定
var id = ns.Get("result").AsInteger();

注意,如果你只使用通过 Engine.Modules.Add 创建的模块,则不需要 EnableModules

.NET 互操作性

  • 从 JavaScript 操作 CLR 对象,包括:
    • 单个值
    • 对象
      • 属性
      • 方法
    • 委托
    • 匿名对象
  • 将 JavaScript 值转换为 CLR 对象
    • 原始值
    • Object -> expando 对象(IDictionary<string, object> 和 dynamic)
    • Array -> object[]
    • Date -> DateTime
    • number -> double
    • string -> string
    • boolean -> bool
    • Regex -> RegExp
    • Function -> Delegate
  • 扩展方法

安全性

以下功能为你提供了一个安全的沙箱环境来运行用户脚本。

  • 定义内存限制,以防止分配耗尽内存。
  • 启用/禁用 BCL 的使用,以防止脚本调用 .NET 代码。
  • 限制语句数量,以防止无限循环。
  • 限制调用深度,以防止深度递归调用。
  • 定义超时,以防止脚本运行时间过长。

分支和发布

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号