QuickJS 集成与实践指南

详细介绍如何在 Node.js 中集成 QuickJS 引擎,包括安装、配置、使用 QuickJS 执行 JavaScript 代码的详细步骤。

目录

第 1 章:QuickJS 概述与特性全览

“在不足 1 MB 的体积中,实现完整 ECMAScript 规范的奇迹。”

1.1 QuickJS 是什么

QuickJS 是一个由 Fabrice Bellard(著名程序员,QEMU、FFmpeg、TinyCC 的作者)与 Charlie Gordon 于 2019 年推出的轻量级 ECMAScript 引擎
它完全遵循 ECMAScript 标准(目前支持到 ES2023 主要特性),能够在几百 KB 的二进制体积内执行现代 JavaScript,包括:

  • async/await 异步语法
  • PromiseProxySymbol
  • BigIntBigFloatBigDecimal
  • 模块系统 (import/export)
  • 垃圾回收、异常系统、原型链等完整特性

QuickJS 设计目标是:

“在极小体积内提供一个完整、可嵌入、可独立运行的现代 JavaScript 环境。”


1.2 为什么选择 QuickJS

1.2.1 相较于 V8 / JavaScriptCore

特性QuickJSV8 / JSC
实现语言纯 C (C99)C++
体积约 600–900 KB20–50 MB
JIT 支持❌ 解释执行
启动时间极快较慢
内存占用极低(< 5 MB)高(> 50 MB)
嵌入复杂度简单(单文件即可)高(需多库依赖)
规范覆盖度完整 ES2023完整
应用场景嵌入式、CLI、沙箱浏览器、Node.js

QuickJS 并非为高性能服务器设计,而是为:

  • 嵌入式系统;
  • 桌面工具;
  • 游戏引擎脚本;
  • 规则引擎 / SaaS 平台;
  • 边缘计算(Edge Runtime)

提供一套安全、轻量、无外部依赖的脚本执行环境。


1.3 核心特性一览

✅ ECMAScript 支持完整

支持所有现代语法特性:

  • class / async / await
  • Proxy / Reflect / Symbol
  • BigInt / BigFloat / BigDecimal
  • 模块系统 (ESM / import/export)
  • 生成器 (generator) / 异步生成器
  • 结构赋值 / 模板字符串

✅ 可嵌入的 Runtime + Context 架构

QuickJS 的核心是两层结构:

层级作用
JSRuntime管理内存、GC、模块缓存、原子表
JSContext保存执行环境、变量作用域、异常状态

一个 Runtime 可包含多个 Context,实现沙箱隔离。

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JS_Eval(ctx, "print('Hello, QuickJS!')", ...);

✅ 字节码编译与独立执行

QuickJS 附带工具 qjsc

  • 将 JS 源码编译为字节码;
  • 或导出为 .c 文件;
  • 可生成独立可执行文件。
qjsc -o hello hello.js
./hello

生成的可执行文件不依赖 Node.js 或其他运行时,非常适合部署与嵌入。

✅ 高精度数值类型(扩展)

QuickJS 独有的 BigFloat / BigDecimal 扩展模块,
支持任意精度浮点计算与算术操作符重载,适合金融/科学计算。

"use math";
let x = 1.234567890123456789p;
let y = 10p;
print(x * y); // 高精度输出

✅ 内建标准模块

模块功能
std文件、命令行、I/O
os操作系统调用
big任意精度运算
worker多线程 worker 支持(实验性)

这些模块在 CLI 模式中可直接启用,在嵌入模式中可自定义。

✅ 可裁剪、可定制

QuickJS 的源代码非常模块化,主要模块包括:

quickjs.c
quickjs-libc.c
libbf.c       // BigFloat/BigDecimal
libregexp.c   // RegExp 实现
libunicode.c  // Unicode 支持
cutils.c

在嵌入式场景可通过裁剪 libbf.clibunicode.c 将体积降至 400KB 以下。

1.4 QuickJS 架构总览图

flowchart TB
    A["JavaScript 源代码"]
    A --> B["Parser / Compiler"]
    B --> C["Bytecode Generator"]
    C --> D["Interpreter (VM Core)"]
    D --> E["Garbage Collector"]
    D --> F["JSRuntime / Context"]
    F --> G["Native API / C Bindings"]
    G --> H["Host Application (C/C++/Go/Rust)"]

1.5 适用场景与行业用途

领域应用场景
嵌入式设备配置脚本、自动化逻辑
游戏引擎AI、UI、剧情脚本
SaaS 系统动态规则、可编程策略引擎
CLI 工具可扩展命令行
Web 平台WASM 内嵌执行器
教育/实验学习 JS 规范与实现

1.6 QuickJS 的设计哲学

Bellard 在 QuickJS 文档中写道:

Small is not about less features, but about perfect balance.

QuickJS 的设计哲学可以总结为:

原则说明
完全标准化不引入非规范语义
可预测不使用多线程或 JIT
可移植单文件 C 实现,无外部依赖
可嵌入清晰 API,支持多 Context
可控内存手动 GC 与可插拔分配器

1.7 QuickJS 与 Duktape / JerryScript 对比

特性QuickJSDuktapeJerryScript
体积~900KB~500KB~200KB
语法覆盖完整 ES2023ES5.1ES5
性能较高中等较低
模块系统✅ ESM
BigInt 支持
异步✅ Promise
C 接口复杂度简洁
嵌入友好度

QuickJS 在语法与功能完整性上远胜同级轻量引擎。

第 2 章:QuickJS 的编译与构建

“QuickJS 是极少数可以一行命令构建、跨平台运行的现代脚本引擎。”

2.1 源码获取与结构概览

QuickJS 的源码非常整洁,通常包含以下文件:

quickjs/
 ├── Makefile
 ├── quickjs.c
 ├── quickjs.h
 ├── quickjs-libc.c
 ├── libbf.c / libbf.h         # BigFloat / BigDecimal
 ├── libregexp.c / libunicode.c # RegExp & Unicode
 ├── cutils.c / list.h         # 工具函数
 ├── qjs.c                     # 解释器 CLI
 ├── qjsc.c                    # 字节码编译器
 ├── run-test262.c             # ECMAScript 测试
 ├── repl.js                   # REPL 示例
 └── README.md

主要目标文件:

  • qjs:解释器
  • qjsc:编译器
  • libquickjs.a:静态库(嵌入项目使用)

2.2 在 Linux 上构建

QuickJS 原生支持 GNU Make:

git clone https://github.com/bellard/quickjs.git
cd quickjs
make

输出:

qjs       # 命令行解释器
qjsc      # 编译器
libquickjs.a

可选参数:

命令说明
make qjs仅编译解释器
make qjsc仅编译编译器
make test运行 ES 测试集
make clean清理构建

2.3 Windows 编译方法

使用 MinGW 或 MSYS2:

pacman -S make gcc
git clone https://github.com/bellard/quickjs.git
cd quickjs
make CC=gcc

生成 qjs.exeqjsc.exe
如需使用 MSVC,可手动创建 quickjs.sln 并包含以下源文件:

quickjs.c
quickjs-libc.c
libbf.c
libregexp.c
libunicode.c
cutils.c

添加 -D_CRT_SECURE_NO_WARNINGS -D__USE_MINGW_ANSI_STDIO 等宏。

2.4 macOS 编译

macOS 自带 clang,可直接构建:

brew install gmake
gmake

可使用 brew install quickjs 安装预编译版本。

2.5 嵌入式裁剪构建

在嵌入式系统中,可按需裁剪:

# mini build
OBJS = quickjs.c cutils.c libregexp.c
CFLAGS += -DCONFIG_NO_BIGNUM

或仅保留核心 VM:

gcc -Os -fdata-sections -ffunction-sections -Wl,--gc-sections \
    -DCONFIG_NO_BIGNUM quickjs.c cutils.c -o mini-qjs

最终体积可控制在 300–400KB。

2.6 跨平台交叉编译

示例:为 ARM64 构建

make CROSS_PREFIX=aarch64-linux-gnu- CC=aarch64-linux-gnu-gcc

输出:

qjs-aarch64
qjsc-aarch64
libquickjs-arm64.a

适用于树莓派、OpenWrt、路由器、嵌入式网关等。

2.7 构建可共享库(动态链接)

QuickJS 默认生成静态库,如需动态库:

gcc -shared -fPIC quickjs.c quickjs-libc.c -o libquickjs.so

嵌入时链接:

gcc main.c -L. -lquickjs -lm -o app

2.8 构建自定义配置

通过宏控制功能启用/禁用:

宏定义含义
CONFIG_BIGNUM启用 BigFloat/BigDecimal
CONFIG_VERSION定义版本号
CONFIG_LTO启用链接时优化
CONFIG_NO_LINE_NUMBERS去除调试信息
CONFIG_NO_OS_MODULE移除 os 模块
CONFIG_NO_STD_MODULE移除 std 模块

示例:

gcc -Os -DCONFIG_NO_OS_MODULE -c quickjs.c

2.9 验证构建结果

./qjs
> console.log("Hello QuickJS");
Hello QuickJS

或:

./qjsc -o test test.js
./test

输出即表明运行成功。

2.10 与项目集成的基础目录结构

在嵌入式项目中常见结构如下:

/project
 ├── src/
 │    ├── main.c
 │    └── bindings.c
 ├── deps/
 │    └── quickjs/
 │         ├── quickjs.c
 │         ├── quickjs.h
 │         └── libbf.c ...
 ├── include/
 ├── build/
 └── Makefile

示例 Makefile:

OBJS = src/main.o src/bindings.o deps/quickjs/quickjs.o
CFLAGS = -Iinclude -Ideps/quickjs -Os
LDFLAGS = -lm
app: $(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

2.11 在 CMake 工程中使用 QuickJS

add_library(quickjs STATIC
    quickjs.c
    quickjs-libc.c
    libbf.c
    libregexp.c
    libunicode.c
    cutils.c)

add_executable(app main.c)
target_link_libraries(app PRIVATE quickjs m)

构建:

cmake -Bbuild && cmake --build build -j

2.12 常见构建问题与解决方案

问题原因解决
undefined reference to pow未链接数学库添加 -lm
malloc_trim 未定义glibc 版本过低移除 malloc_trim 调用
Unicode 编译错误缺少 libunicode.c添加文件或禁用 Unicode
文件过大启用 -Os + 去除调试信息
ARM 编译失败未定义 endian 宏添加 -D__LITTLE_ENDIAN__

2.13 QuickJS-NG 与官方差异

QuickJS-NG 是社区维护版本,特点:

  • 更新到 ES2023;
  • 支持更多系统(Windows, macOS);
  • 新增模块加载器;
  • 修复 GC 泄漏;
  • 添加 CMake 构建支持。

安装:

git clone https://github.com/quickjs-ng/quickjs.git
cmake -Bbuild -DCONFIG_BIGNUM=ON
cmake --build build

2.14 构建总结

类型推荐构建方式
通用 Linuxmake
Windows (MSYS2)make CC=gcc
macOSgmake 或 brew install
嵌入式-Os + CONFIG_NO_BIGNUM
跨平台CMake
动态库gcc -shared -fPIC

第 3 章:QuickJS 基础 API 使用与运行时模型

“在 QuickJS 中,Runtime 是世界,Context 是宇宙的一个快照。

3.1 Runtime / Context 基本概念

QuickJS 的执行模型分为两层:

层级概念职责
JSRuntime全局运行时环境管理内存、GC、模块缓存、字符串原子池、JobQueue
JSContext执行上下文保存作用域链、变量、函数、异常、全局对象

每个 JSRuntime 可包含多个 JSContext
例如,一个 SaaS 平台可以为每个租户创建独立 Context。

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);

销毁:

JS_FreeContext(ctx);
JS_FreeRuntime(rt);

3.2 Runtime 生命周期与 GC 管理

创建与销毁

JSRuntime *rt = JS_NewRuntime();
JS_SetMaxStackSize(rt, 1 << 20); // 设置栈上限(1MB)
JS_FreeRuntime(rt);

手动触发 GC

JS_RunGC(rt);

自定义内存分配器

JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque);

你可以接入自己的分配器(如内存池或统计分配)。

3.3 Context 的创建与作用

JSContext 是运行 JavaScript 的独立空间。
它持有:

  • 全局对象;
  • 当前模块系统;
  • 执行栈;
  • 异常信息。

创建

JSContext *ctx = JS_NewContext(rt);

获取全局对象

JSValue global = JS_GetGlobalObject(ctx);

执行脚本

const char *code = "let x = 3 + 4; x;";
JSValue result = JS_Eval(ctx, code, strlen(code),
                         "<input>", JS_EVAL_TYPE_GLOBAL);

3.4 运行 JS 脚本的多种方式

函数说明
JS_Eval执行源码(字符串)
JS_EvalFile执行文件内容
JS_EvalFunction执行已编译的函数
JS_Call调用 JS 函数对象
JS_NewCFunction创建 C 函数并注册

示例:

JSValue result = JS_Eval(ctx, "1 + 2 * 3", -1, "<eval>", JS_EVAL_TYPE_GLOBAL);
int32_t val;
JS_ToInt32(ctx, &val, result);
printf("Result: %d\n", val);  // 输出 7
JS_FreeValue(ctx, result);

3.5 JSValue 类型系统

QuickJS 所有 JS 对象都用 JSValue 表示。
内部是一个带类型标签的联合体。

typedef union JSValueUnion {
    int32_t int32;
    double float64;
    void *ptr;
#if JS_SHORT_BIG_INT_BITS == 32
    int32_t short_big_int;
#else
    int64_t short_big_int;
#endif
} JSValueUnion;

typedef struct JSValue {
    JSValueUnion u;
    int64_t tag;
} JSValue;

常用类型判定函数:

函数判断类型
JS_IsUndefined(v)undefined
JS_IsNull(v)null
JS_IsBool(v)布尔
JS_IsNumber(v)数字
JS_IsString(v)字符串
JS_IsObject(v)对象
JS_IsFunction(ctx,v)函数

3.6 从 JSValue 到 C 类型的转换

函数功能
JS_ToBool(ctx, val)转换为 bool
JS_ToInt32(ctx, &dst, val)转换为 int
JS_ToInt64(ctx, &dst, val)转换为 long
JS_ToFloat64(ctx, &dst, val)转换为 double
JS_ToCString(ctx, val)转换为 C 字符串
JS_FreeCString(ctx, str)释放字符串

示例:

JSValue result = JS_Eval(ctx, "3.14 * 2", -1, "<eval>", JS_EVAL_TYPE_GLOBAL);
double d;
JS_ToFloat64(ctx, &d, result);
printf("%f\n", d);
JS_FreeValue(ctx, result);

3.7 从 C 到 JS 的值构造

函数构造类型
JS_NewBool(ctx, b)布尔
JS_NewInt32(ctx, n)整数
JS_NewFloat64(ctx, f)浮点数
JS_NewString(ctx, str)字符串
JS_NewObject(ctx)空对象
JS_NewArray(ctx)空数组

示例:

JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, "Alice"));
JS_SetPropertyStr(ctx, obj, "age", JS_NewInt32(ctx, 23));

3.8 属性访问与修改

JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "score", JS_NewInt32(ctx, 99));
JSValue val = JS_GetPropertyStr(ctx, obj, "score");
int32_t score;
JS_ToInt32(ctx, &score, val);

也可以使用索引:

JS_SetPropertyUint32(ctx, arr, 0, JS_NewInt32(ctx, 10));

3.9 异常处理机制

QuickJS 没有 C 层异常传播,
所有错误都返回 JS_EXCEPTION,需手动检查。

JSValue ret = JS_Eval(ctx, "throw new Error('oops')", -1, "<eval>", 0);
if (JS_IsException(ret)) {
    JSValue exc = JS_GetException(ctx);
    const char *msg = JS_ToCString(ctx, exc);
    fprintf(stderr, "Error: %s\n", msg);
    JS_FreeCString(ctx, msg);
    JS_FreeValue(ctx, exc);
}

3.10 JobQueue 与 Promise 驱动

QuickJS 的事件循环由 JS_ExecutePendingJob() 负责:

JSContext *job_ctx;
while (JS_IsJobPending(rt)) {
    JS_ExecutePendingJob(rt, &job_ctx);
}

可手动集成到你的宿主事件循环中,实现 async/await。

3.11 模块加载与上下文隔离

每个 JSContext 都拥有独立模块表,可实现脚本沙箱。

JSContext *sandbox = JS_NewContext(rt);
JS_Eval(sandbox, "print('isolated');", -1, "<eval>", JS_EVAL_TYPE_GLOBAL);

3.12 销毁顺序与资源回收

1️⃣ 释放所有 JSValue

JS_FreeValue(ctx, val);

2️⃣ 释放 Context:

JS_FreeContext(ctx);

3️⃣ 最后释放 Runtime:

JS_FreeRuntime(rt);

⚠️ 注意:不要在 GC 过程中释放 Context,否则会崩溃。

第 4 章:C/C++ 与 JavaScript 的交互机制

“C 是地基,JS 是灵魂。QuickJS 的嵌入点,是这两者的桥梁。”

4.1 注册 C 函数到 JS

注册方式核心是 JS_NewCFunction + JS_SetPropertyStr

static JSValue js_add(JSContext *ctx, JSValueConst this_val,
                      int argc, JSValueConst *argv)
{
    int a, b;
    JS_ToInt32(ctx, &a, argv[0]);
    JS_ToInt32(ctx, &b, argv[1]);
    return JS_NewInt32(ctx, a + b);
}

void register_functions(JSContext *ctx)
{
    JSValue global = JS_GetGlobalObject(ctx);
    JS_SetPropertyStr(ctx, global, "add",
        JS_NewCFunction(ctx, js_add, "add", 2));
    JS_FreeValue(ctx, global);
}

运行:

console.log(add(2,3)); // 5

4.2 暴露 C 模块到 JS

可以使用模块定义器注册一整个 C 模块。

static const JSCFunctionListEntry mylib_funcs[] = {
    JS_CFUNC_DEF("sum", 2, js_add),
    JS_PROP_STRING_DEF("version", "1.0", 0),
    JS_PROP_INT32_DEF("magic", 42, 0),
};

static int js_mylib_init(JSContext *ctx, JSModuleDef *m)
{
    return JS_SetModuleExportList(ctx, m, mylib_funcs,
                                  sizeof(mylib_funcs) / sizeof(JSCFunctionListEntry));
}

JSModuleDef *js_init_module_mylib(JSContext *ctx, const char *name)
{
    JSModuleDef *m = JS_NewCModule(ctx, name, js_mylib_init);
    JS_AddModuleExportList(ctx, m, mylib_funcs,
                           sizeof(mylib_funcs) / sizeof(JSCFunctionListEntry));
    return m;
}

在 JS 端即可:

import * as mylib from 'mylib';
console.log(mylib.version, mylib.sum(3,4));

4.3 从 JS 调用 C 函数并返回对象

static JSValue js_make_player(JSContext *ctx, JSValueConst this_val,
                              int argc, JSValueConst *argv)
{
    JSValue obj = JS_NewObject(ctx);
    JS_SetPropertyStr(ctx, obj, "name", JS_NewString(ctx, "Alice"));
    JS_SetPropertyStr(ctx, obj, "hp", JS_NewInt32(ctx, 100));
    return obj;
}
let p = make_player();
console.log(p.name, p.hp);

4.4 从 C 调用 JS 函数

获取函数引用

JSValue global = JS_GetGlobalObject(ctx);
JSValue func = JS_GetPropertyStr(ctx, global, "onEvent");
JSValue arg = JS_NewString(ctx, "test");
JS_Call(ctx, func, JS_UNDEFINED, 1, &arg);
JS_FreeValue(ctx, arg);
JS_FreeValue(ctx, func);
JS_FreeValue(ctx, global);

绑定回调对象

可以将 C 指针作为 opaque 绑定在 JS 对象中,用于回调时恢复 C 对象上下文。

4.5 JS <-> C 结构体映射(opaque)

QuickJS 提供 JS_SetOpaque()JS_GetOpaque() 用于在对象上保存 C 指针。

typedef struct {
    int id;
    char name[32];
} Player;

static JSClassID player_class_id;

static void player_finalizer(JSRuntime *rt, JSValue val)
{
    Player *p = JS_GetOpaque(val, player_class_id);
    free(p);
}

static JSClassDef player_class = {
    "Player",
    .finalizer = player_finalizer,
};

void register_player_class(JSContext *ctx)
{
    JS_NewClassID(&player_class_id);
    JS_NewClass(JS_GetRuntime(ctx), player_class_id, &player_class);

    JSValue proto = JS_NewObject(ctx);
    JS_SetPropertyFunctionList(ctx, proto, NULL, 0);
    JS_SetClassProto(ctx, player_class_id, proto);
}

创建对象:

Player *p = malloc(sizeof(Player));
p->id = 1;
strcpy(p->name, "Hero");

JSValue obj = JS_NewObjectClass(ctx, player_class_id);
JS_SetOpaque(obj, p);

从 JS 调用:

console.log(player.name); // "Hero"

4.6 处理 C 层异常

在 C 层可返回异常对象:

return JS_ThrowTypeError(ctx, "Invalid argument");

JS 层将自动捕获为异常。

4.7 在 JS 模块中注册全局方法

JSValue global = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global, "print",
    JS_NewCFunction(ctx, js_print, "print", 1));

示例实现:

static JSValue js_print(JSContext *ctx, JSValueConst this_val,
                        int argc, JSValueConst *argv)
{
    for (int i=0; i<argc; i++) {
        const char *str = JS_ToCString(ctx, argv[i]);
        printf("%s ", str);
        JS_FreeCString(ctx, str);
    }
    printf("\n");
    return JS_UNDEFINED;
}

4.8 在 C++ 项目中封装 QuickJS

你可以封装一个简单的 RAII 类:

class QuickJSContext {
public:
    QuickJSContext() {
        rt = JS_NewRuntime();
        ctx = JS_NewContext(rt);
    }
    ~QuickJSContext() {
        JS_FreeContext(ctx);
        JS_FreeRuntime(rt);
    }
    void eval(const std::string& code) {
        JS_Eval(ctx, code.c_str(), code.size(), "<eval>", JS_EVAL_TYPE_GLOBAL);
    }
private:
    JSRuntime* rt;
    JSContext* ctx;
};

4.9 通过 C 调用异步 Promise

QuickJS 的 async 函数返回 Promise 对象,可通过 C 手动执行任务:

JSValue promise = JS_Eval(ctx, "async function f(){return 42;} f()", -1, "<eval>", 0);
while (JS_IsJobPending(rt)) {
    JS_ExecutePendingJob(rt, &ctx);
}

获取结果:

int64_t result;
JS_ToInt64(ctx, &result, promise);

4.10 复杂结构交互实例:注册事件系统

static JSValue js_emit(JSContext *ctx, JSValueConst this_val,
                       int argc, JSValueConst *argv)
{
    const char *event = JS_ToCString(ctx, argv[0]);
    const char *msg = JS_ToCString(ctx, argv[1]);
    printf("[Event] %s -> %s\n", event, msg);
    JS_FreeCString(ctx, event);
    JS_FreeCString(ctx, msg);
    return JS_UNDEFINED;
}

void register_event_system(JSContext *ctx)
{
    JSValue global = JS_GetGlobalObject(ctx);
    JS_SetPropertyStr(ctx, global, "emit",
        JS_NewCFunction(ctx, js_emit, "emit", 2));
    JS_FreeValue(ctx, global);
}

使用:

emit("player.join", "Alice");

4.11 嵌入式最佳实践

建议
Context每个独立任务/租户使用独立 Context
资源回收所有 JSValue 必须释放
错误处理统一封装 JS_IsException
异步任务手动执行 JobQueue
沙箱化禁止导入 osstd 模块
性能尽量减少跨语言调用

第 5 章:模块系统与字节码编译器 qjsc

“QuickJS 的模块系统既是 ECMAScript 标准的一部分,也是嵌入式系统实现脚本复用、热更新与沙箱的关键。”

5.1 QuickJS 模块体系概述

QuickJS 支持完整 ECMAScript Module(ESM)语义:

  • import / export 语法;
  • 模块依赖解析;
  • 模块缓存与懒加载;
  • 动态导入(import());
  • 支持 C 扩展模块(JS_NewCModule);
  • 可重定义模块加载器函数。

模块加载流程(简化)

sequenceDiagram
    participant A as JSContext
    participant B as ModuleLoader
    participant C as JSRuntime
    A->>B: resolve(import_path)
    B-->>A: 返回源码或字节码
    A->>C: instantiate module
    C-->>A: 执行并缓存结果

5.2 JS 层模块示例

// math.js
export function add(a, b) { return a + b; }
export const PI = 3.14159;

// main.js
import { add, PI } from './math.js';
console.log(add(2, 3) * PI);

使用 CLI:

qjs main.js

QuickJS 的 import 语义完全与浏览器 / Node.js 一致,
但解析逻辑由宿主提供的 module_loader_func 决定。

5.3 自定义模块加载器(C 层)

默认情况下,QuickJS 在嵌入模式下不会自动解析 import
你必须注册自己的模块加载器:

static JSModuleDef *module_loader(JSContext *ctx,
                                  const char *module_name, void *opaque)
{
    // 读取模块源码
    char *buf = load_file(module_name);
    JSValue func_val = JS_Eval(ctx, buf, strlen(buf),
                               module_name, JS_EVAL_TYPE_MODULE);
    free(buf);
    return JS_VALUE_GET_PTR(func_val);
}

void setup_loader(JSRuntime *rt)
{
    JS_SetModuleLoaderFunc(rt, NULL, module_loader, NULL);
}

这样,你可以控制模块查找路径、缓存策略与权限。

5.4 模块缓存与热更新机制

QuickJS 会将已加载模块缓存在 Runtime 内部。
再次导入同名模块时直接返回缓存对象。

import * as math from './math.js';
console.log(math.PI);

在 C 层,可清理模块缓存:

JS_ClearModuleRegistry(rt);

实现热更新策略:

  1. 清理缓存;
  2. 重新加载模块;
  3. 重新执行导入。

5.5 C 模块与原生扩展

你可以直接在 C 中定义一个模块:

static JSValue js_hello(JSContext *ctx, JSValueConst this_val,
                        int argc, JSValueConst *argv)
{
    printf("Hello from C!\n");
    return JS_UNDEFINED;
}

static const JSCFunctionListEntry mylib_funcs[] = {
    JS_CFUNC_DEF("hello", 0, js_hello),
};

static int js_mylib_init(JSContext *ctx, JSModuleDef *m)
{
    return JS_SetModuleExportList(ctx, m, mylib_funcs, 1);
}

JSModuleDef *js_init_module_mylib(JSContext *ctx, const char *name)
{
    JSModuleDef *m = JS_NewCModule(ctx, name, js_mylib_init);
    JS_AddModuleExportList(ctx, m, mylib_funcs, 1);
    return m;
}

JavaScript 调用:

import * as mylib from 'mylib';
mylib.hello();

5.6 动态导入(异步 import)

QuickJS 支持动态导入:

async function load() {
  const math = await import('./math.js');
  console.log(math.add(2,3));
}
load();

C 层循环:

while (JS_IsJobPending(rt)) {
    JS_ExecutePendingJob(rt, &ctx);
}

这允许在 SaaS 或插件系统中实现 动态脚本加载与懒执行

5.7 字节码编译器 qjsc 基础

qjsc 是 QuickJS 的字节码编译器,可将 JS 源码转换为:

  • 可执行文件;
  • C 源码;
  • 字节码二进制文件。

命令行用法

命令功能
qjsc -o out.c input.js编译为 C 源码
qjsc -o app input.js直接生成可执行
qjsc -e启用 ESM 模式
qjsc -m强制模块输出
qjsc -fbignum启用 BigInt 支持
qjsc -D添加 debug info

示例:

qjsc -o hello.c hello.js
gcc -O2 hello.c -lquickjs -lm -o hello
./hello

或:

qjsc -o hello hello.js
./hello

5.8 内嵌多模块编译

可以编译多个 JS 文件并自动合并为 C 源文件:

qjsc -o bundle.c main.js math.js utils.js

嵌入后,运行时可直接加载内嵌模块。

5.9 字节码文件加载

QuickJS 支持加载 .c.bin 形式的预编译脚本:

extern const uint8_t qjsc_test[];
extern const uint32_t qjsc_test_size;

JSValue obj = JS_ReadObject(ctx, qjsc_test, qjsc_test_size, JS_READ_OBJ_BYTECODE);
JSValue val = JS_EvalFunction(ctx, obj);

这适合 资源受限环境,无需运行时解析源码。

5.10 模块系统嵌入架构示意

flowchart LR
    A["Host App (C)"] --> B["QuickJS Runtime"]
    B --> C["Context"]
    C --> D["Module Loader (C)"]
    D --> E["JS Module Registry"]
    E --> F["Compiled Bytecode / C Module"]

5.11 实战示例:嵌入式脚本插件系统

void load_plugin(JSContext *ctx, const char *path) {
    FILE *fp = fopen(path, "r");
    fseek(fp, 0, SEEK_END);
    size_t len = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    char *buf = malloc(len+1);
    fread(buf, 1, len, fp);
    buf[len] = '\0';
    JSValue v = JS_Eval(ctx, buf, len, path, JS_EVAL_TYPE_MODULE);
    free(buf);
    if (JS_IsException(v)) { JSValue e = JS_GetException(ctx); ... }
}

5.12 模块系统总结

特性说明
模块语法完整 ES Module
C 模块扩展JS_NewCModule
模块缓存Runtime 内部维护
动态导入支持 Promise 模式
自定义加载JS_SetModuleLoaderFunc
字节码编译qjsc 支持生成 .c / 可执行

第 6 章:内存管理与 GC 调优

“GC 是 QuickJS 的心跳,它让脚本永不泄露,也可能让性能止步。”

6.1 QuickJS 的内存管理模型

QuickJS 采用 引用计数 + 标记清除 GC 结合机制:

flowchart TB
    A["JSValue"] --> B["引用计数 (RC)"]
    A --> C["GC 标记"]
    C --> D["JS_RunGC 触发清理"]
  • 引用计数:即时释放对象;
  • 标记清除:周期性扫描全图。

6.2 内存分配器与可插拔机制

所有内存操作都通过 JSMallocFunctions 抽象:

typedef struct JSMallocState {
    size_t malloc_count;
    size_t malloc_size;
    size_t malloc_limit;
    void *opaque; /* user opaque */
} JSMallocState;

typedef struct JSMallocFunctions {
    void *(*js_malloc)(JSMallocState *s, size_t size);
    void (*js_free)(JSMallocState *s, void *ptr);
    void *(*js_realloc)(JSMallocState *s, void *ptr, size_t size);
    size_t (*js_malloc_usable_size)(const void *ptr);
} JSMallocFunctions;

注册:

// JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque);
JSRuntime *JS_NewRuntime2(&my_alloc_funcs, NULL);

可接入:

  • jemalloc;
  • 内存池;
  • 自定义追踪系统(用于统计分配总量)。

6.3 垃圾回收触发条件

QuickJS 通过以下逻辑触发 GC:

  1. 每次分配后更新计数;
  2. allocated_bytes > malloc_limit 时触发;
  3. 调用 JS_RunGC(rt) 强制触发。

你可修改阈值:

JS_SetMemoryLimit(rt, 32 * 1024 * 1024); // 32MB

6.4 手动控制 GC

函数功能
JS_RunGC(rt)立即触发 GC
JS_MarkValue(ctx, val, mark_func)手动标记对象
JS_IsLiveObject()检查对象状态
JS_GCStats(rt)获取统计信息

6.5 内存分配监控

QuickJS 提供 js_trace_malloc 调试功能:

void *js_trace_malloc(void *opaque, size_t size)
{
    printf("[alloc] %zu bytes\n", size);
    return malloc(size);
}

配合 JS_NewRuntime2() 使用。

6.6 内存分布示意

区域内容
Heap对象、数组、字符串
Atom Table全局字符串池
Module Cache已加载模块
Function Cache字节码函数
JobQueue异步任务
Context Stack当前执行栈

6.7 内存泄漏防御

原则 1:所有 JSValue 必须释放

JS_FreeValue(ctx, value);

原则 2:所有 C 分配的指针必须注册 Finalizer

JS_SetOpaque(obj, p);
JS_NewClass(rt, id, &(JSClassDef){.finalizer = my_free});

原则 3:Context 必须在 Runtime 之前释放

JS_FreeContext(ctx);
JS_FreeRuntime(rt);

6.8 常见 GC 问题排查

现象可能原因解决方案
内存不断上升未释放 JSValue检查局部变量
对象回收异常JS_SetOpaque 未注册 finalizer添加 class finalizer
崩溃释放顺序错误Context → Runtime 顺序
跨 Context 对象失效不可混用 Context 对象重新 clone

6.9 内存调优建议

场景策略
嵌入式禁用 BigNum、Unicode
高并发多 Runtime 分片
SaaS 多租户每租户一个 Context
长时间运行定期 JS_RunGC
性能关键使用字节码模式 (qjsc)

6.10 GC 性能测量

示例:监控 GC 延迟

double start = now();
JS_RunGC(rt);
printf("GC took %.3f ms\n", now() - start);

结果通常在:

  • 小型脚本:0.1–0.5 ms;
  • 大型脚本:2–5 ms;
  • 极端情况:10ms+。

6.11 Finalizer 的使用

static void player_finalizer(JSRuntime *rt, JSValue val)
{
    Player *p = JS_GetOpaque(val, player_class_id);
    if (p) free(p);
}

GC 扫描到无引用对象时自动触发。
适合关闭文件、socket、释放 native 内存等场景。

6.12 多 Runtime 模式

QuickJS 不支持线程安全的并发访问。
若需多线程,应创建多个 Runtime。

void *thread(void *arg) {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);
    JS_Eval(ctx, "doWork()", -1, "<eval>", 0);
    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
}

不同 Runtime 之间互不干扰,可并行执行。

6.13 内存调试工具链

QuickJS 自带调试选项:

make DEBUG=1

输出 GC/alloc 信息。
也可配合 valgrindasan 检查泄漏:

valgrind ./qjs test.js

6.14 嵌入式内存策略

在嵌入式设备上建议:

  • 限制字符串池大小;
  • 禁用 BigFloat;
  • 使用 -Os 编译;
  • 控制堆上限;
  • 每帧调用 JS_RunGC()

6.15 小结

QuickJS 特点
GC 模型引用计数 + 标记清除
可控性高(手动/自动均可)
可扩展性支持自定义 allocator
优化方向减少跨 Context 引用
嵌入特性轻量、可裁剪
安全性Finalizer 管理原生资源

第 7 章:QuickJS 多语言绑定与跨语言集成实践

“QuickJS 的真正力量,不仅在于它能嵌入 C 程序,还在于它能成为 Go、Rust、Python、C++ 等生态的脚本桥梁。”

7.1 为什么要多语言绑定

虽然 QuickJS 的官方 API 是 C 语言接口,但在现代系统中,
很多业务逻辑与主框架并非用 C 开发,例如:

  • Go:用于 SaaS、服务端逻辑;
  • Rust:用于高性能、安全型系统;
  • Python:用于 AI 工具或脚本自动化;
  • C++:用于游戏引擎、桌面应用。

为了在这些语言中使用 QuickJS,需要通过 FFI(Foreign Function Interface)binding 层 实现调用。

7.2 QuickJS 的 FFI 原理

QuickJS 的 FFI 逻辑通常遵循三层结构:

flowchart TB
    A["主语言(Go/Rust/Python)"] --> B["FFI 绑定层(CGo/Rust FFI/PyBind11)"]
    B --> C["QuickJS C API (libquickjs.a)"]
    C --> D["JS Runtime + Context"]

这样即可从高层语言调用 QuickJS 运行 JS 脚本,或在 JS 中调用主语言函数。

7.3 Go 与 QuickJS 集成

7.3.1 绑定方案概述

Go 调用 QuickJS 通常有三种方案:

方案描述优点缺点
CGo 调用官方 C API直接封装 QuickJS性能高、稳定编译复杂、跨平台困难
现有封装库 github.com/quickjs-go/quickjs使用现成绑定快速上手功能略少
通过插件 (c-shared) 模式编译为 .so与 Go 模块解耦通信开销较大

7.3.2 Go 示例:执行 JS 代码

package main

/*
#cgo CFLAGS: -I./quickjs
#cgo LDFLAGS: -L./quickjs -lquickjs -lm
#include "quickjs.h"
*/
import "C"
import "unsafe"

func main() {
	rt := C.JS_NewRuntime()
	ctx := C.JS_NewContext(rt)

	code := C.CString(`let x = 5 * 10; x;`)
	defer C.free(unsafe.Pointer(code))

	res := C.JS_Eval(ctx, code, C.strlen(code), C.CString("<eval>"), C.JS_EVAL_TYPE_GLOBAL)
	var val C.int32_t
	C.JS_ToInt32(ctx, &val, res)
	println("Result:", int(val))

	C.JS_FreeValue(ctx, res)
	C.JS_FreeContext(ctx)
	C.JS_FreeRuntime(rt)
}

输出:

Result: 50

7.3.3 Go 层包装接口示例

type QuickJS struct {
	rt  *C.JSRuntime
	ctx *C.JSContext
}

func NewQuickJS() *QuickJS {
	return &QuickJS{
		rt:  C.JS_NewRuntime(),
		ctx: C.JS_NewContext(C.JS_NewRuntime()),
	}
}

func (q *QuickJS) Eval(code string) string {
	cCode := C.CString(code)
	defer C.free(unsafe.Pointer(cCode))
	res := C.JS_Eval(q.ctx, cCode, C.strlen(cCode), C.CString("<go>"), C.JS_EVAL_TYPE_GLOBAL)
	cStr := C.JS_ToCString(q.ctx, res)
	defer C.JS_FreeCString(q.ctx, cStr)
	return C.GoString(cStr)
}

7.4 Rust 与 QuickJS 集成

Rust 生态下已有多个成熟绑定项目:

地址特点
rquickjshttps://github.com/theduke/rquickjs高层封装、支持 async、安全抽象
quickjs-rshttps://github.com/quickjs-rs/quickjs-rs轻量绑定、接近 C API
boaRust 实现 JS 引擎非 QuickJS,但理念相似

7.4.1 使用 rquickjs 执行脚本

use rquickjs::{Runtime, Context, Value};

fn main() -> rquickjs::Result<()> {
    let rt = Runtime::new()?;
    let ctx = Context::full(&rt)?;
    ctx.eval::<Value, _>("let x = 6 * 7; x")?;
    let result: i32 = ctx.eval("x")?;
    println!("Result = {}", result);
    Ok(())
}

输出:

Result = 42

7.4.2 注册 Rust 函数到 JS

use rquickjs::{Context, Runtime, function::Func};

fn greet(name: String) -> String {
    format!("Hello, {}", name)
}

fn main() -> rquickjs::Result<()> {
    let rt = Runtime::new()?;
    let ctx = Context::full(&rt)?;
    ctx.globals().set("greet", Func::from(greet))?;
    ctx.eval::<(), _>("console.log(greet('World'))")?;
    Ok(())
}

输出:

Hello, World

7.4.3 Rust 异步任务集成

rquickjs 允许直接使用 async:

ctx.with(|ctx| async move {
    let code = "await Promise.resolve(123)";
    let result: i32 = ctx.eval(code).await?;
    println!("Async result: {}", result);
    Ok(())
});

7.5 Python 与 QuickJS 集成

Python 下有现成封装库:

功能
pyquickjs调用 QuickJS C API
quickjs(pypi)内置轻量封装

7.5.1 安装与使用

pip install quickjs

示例:

import quickjs

ctx = quickjs.Context()
result = ctx.eval("2 ** 8")
print(result)

输出:

256

7.5.2 Python 调用 JS 函数

ctx = quickjs.Context()
ctx.add_callable("py_print", lambda msg: print(f"[JS->Python] {msg}"))
ctx.eval("""
py_print('Hello from JS')
""")

7.5.3 JS 调用 Python 模块(扩展)

通过 add_callable 可将 Python 函数注册进 JS 全局对象,实现双向通信。

7.6 多语言绑定的统一设计建议

原则说明
保持宿主语言对象生命周期独立QuickJS 不自动管理主语言资源
所有 JSValue 必须显式释放避免内存泄漏
跨线程隔离 Runtime不共享 Context
统一接口层为多语言封装一套中间层 C API

7.7 Go/Rust 混合架构图

flowchart TB
    A["Go 服务层"] --> B["QuickJS Wrapper (CGo)"]
    B --> C["libquickjs.a"]
    C --> D["JS Context"]
    D --> E["User Scripts"]
    A --> F["Rust 模块"]
    F --> B

7.8 多语言绑定性能评估

场景QuickJS 原生Go 绑定Rust 绑定Python 绑定
单次脚本执行1.0x1.3x1.1x1.5x
函数回调1.0x1.2x1.05x1.6x
跨语言通信较快较慢
内存占用

7.9 小结

  • QuickJS 具备高度可移植性,可在几乎所有语言生态中使用;
  • Go/Rust 是最常见宿主;
  • 通过 FFI 可在游戏引擎、云函数、边缘服务中嵌入执行逻辑。

第 8 章:QuickJS 在嵌入式与 IoT 场景中的应用

“在内存只有 8MB 的设备上,运行完整 ES2023 JavaScript,不再是梦想。”

8.1 为什么 QuickJS 适合嵌入式

特点优势说明
纯 C 实现无第三方依赖,方便移植
体积小几百 KB 即可运行
可裁剪模块化结构,可禁用 BigNum、Unicode
快速启动100ms 内启动
内存可控可设定上限
安全沙箱可完全封闭 OS 调用

这些特性让 QuickJS 在 IoT、边缘计算、嵌入式终端中表现出色。

8.2 嵌入式系统中的典型架构

flowchart LR
    A["设备固件 (C)"] --> B["QuickJS Runtime"]
    B --> C["配置脚本 / 控制逻辑"]
    C --> D["传感器 / 通信驱动"]

QuickJS 作为“脚本层”,解耦了固件逻辑与可变控制。

8.3 构建极简 QuickJS 内核

最小构建配置:

gcc -Os quickjs.c cutils.c libregexp.c -DCONFIG_NO_BIGNUM -o mini-qjs

体积仅约 350 KB。

禁用项:

  • BigFloat / Decimal;
  • Unicode;
  • OS / std 模块。

8.4 在 OpenWrt 上运行 QuickJS

1️⃣ 安装交叉编译工具:

opkg install gcc make

2️⃣ 编译:

make CROSS_PREFIX=mipsel-openwrt-linux- CC=mipsel-openwrt-linux-gcc

3️⃣ 上传 qjs/usr/bin
执行:

qjs -e "print('IoT Ready!')"

8.5 在 ESP32 / STM32 上集成 QuickJS

QuickJS 可裁剪为 “Micro QuickJS”:

  • 编译时使用 -DCONFIG_STACK_CHECK -DCONFIG_NO_BIGNUM
  • 限制栈大小 256KB;
  • 关闭文件系统支持;
  • 使用 malloc 替换 RTOS 内存池。

示例:控制设备 LED

function blink(times) {
  for (let i = 0; i < times; i++) {
    gpio.write(LED_PIN, 1);
    sleep(500);
    gpio.write(LED_PIN, 0);
    sleep(500);
  }
}

blink(3);

嵌入式系统中通过注册 gpio.write C 函数即可。

8.6 在路由器 / 边缘节点中的使用

QuickJS 常用于:

  • 动态规则(ACL、防火墙脚本);
  • 数据采集规则引擎;
  • 设备远程控制逻辑。

示例:

if (packet.src == "192.168.1.10") drop();

8.7 性能与功耗对比(IoT 实测)

项目QuickJSLuaPython (MicroPython)
二进制体积~400 KB~300 KB~600 KB
内存占用2–5 MB1–2 MB4–8 MB
启动时间80–150 ms50–100 ms200–400 ms
功耗影响极低
可读性JS 标准Lua 特有Python 风格

QuickJS 在兼顾可读性与标准化方面更优。

8.8 嵌入式沙箱与权限控制

在 IoT 场景,必须禁止危险模块:

JS_SetModuleLoaderFunc(rt, NULL, safe_loader, NULL);
JS_SetMemoryLimit(rt, 8 * 1024 * 1024);
JS_SetMaxStackSize(rt, 1 << 17);

只暴露安全 API,例如:

register_function(ctx, "readTemp", js_read_temp);
register_function(ctx, "setRelay", js_set_relay);

8.9 热更新与远程脚本执行

QuickJS 可从服务器下载并执行脚本:

download_js("/tmp/update.js");
JS_EvalFile(ctx, "/tmp/update.js", JS_EVAL_TYPE_GLOBAL);

通过签名校验与沙箱机制防止恶意脚本攻击。

8.10 案例:工业控制脚本引擎

结构:

main.c
 ├── quickjs/
 ├── scripts/
 │    ├── rules.js
 │    └── sensors.js
 └── build.sh

运行逻辑:

  1. 初始化硬件;
  2. 启动 QuickJS;
  3. 执行 JS 控制脚本;
  4. 定期运行 GC;
  5. 接收远程脚本更新。

8.11 案例:智能家居设备逻辑

if (motion.detected() && !light.on()) {
  light.on();
  setTimeout(() => light.off(), 30000);
}

这种脚本可由用户在线配置,设备端执行。
QuickJS 提供了高可维护性与灵活性。

8.12 嵌入式优化清单

优化项建议
编译优化-Os -ffunction-sections -fdata-sections
禁用模块CONFIG_NO_BIGNUMCONFIG_NO_STD_MODULE
减少内存降低堆限制、关闭 Unicode
启动优化预加载字节码
热更新使用签名校验
稳定性定期 GC、异常捕获

8.13 QuickJS 与 Lua 在 IoT 场景的对比

指标QuickJSLua
语法ES 标准Lua 特有
可读性
生态前端丰富游戏嵌入成熟
模块化ESMrequire
异步Promisecoroutine
工业可维护性更优稳定
教育易用性
热更新支持支持

8.14 嵌入式架构拓扑示意

flowchart TB
    A["设备主控 (MCU)"] --> B["QuickJS VM"]
    B --> C["本地 JS 脚本"]
    B --> D["云端脚本下发"]
    B --> E["设备驱动层"]
    E --> F["传感器/执行器"]
    D --> B

8.15 小结

  • QuickJS 可在 32 位、64 位平台高效运行;
  • 在 IoT、路由器、智能设备中已被广泛采用;
  • 通过裁剪与沙箱化,可实现安全、稳定、低功耗脚本系统;
  • 与 Lua 相比,QuickJS 拥有更高通用性与可扩展性。

第 9 章:QuickJS 在 SaaS 与工具系统中的应用实践

“QuickJS 是 SaaS 系统中最轻量的‘内嵌智能层’,它让你的用户编写逻辑,而你保持可控。”

9.1 SaaS 场景下的脚本化需求

现代 SaaS 系统广泛采用“可编程逻辑”以增强可定制性:

需求场景说明
表单系统用户自定义计算逻辑(如税率、积分)
工作流引擎条件判断与动态任务分支
报表工具用户自定义聚合与转换
游戏或广告系统策略引擎与事件脚本
AI 应用Prompt 动态拼装与执行控制

QuickJS 以“纯 C 实现 + 高兼容性 + 沙箱控制”成为 SaaS 系统内最常用的可控脚本执行引擎

9.2 QuickJS 在云端的运行模型

SaaS 集成架构示意

flowchart LR
    A["Web/Client"] --> B["API Server"]
    B --> C["Script Engine Layer (QuickJS)"]
    C --> D["Business Logic Hooks"]
    D --> E["DB / Cache / Message Bus"]

SaaS 系统中通常:

  • 每个租户或规则实例创建独立 JSContext
  • JSRuntime 共用;
  • 执行完成后清理上下文;
  • 通过 C 层注册安全 API 与资源限制。

9.3 示例:表单计算引擎

// 用户配置脚本
function calc(data) {
  if (data.type === "vip") {
    return data.price * 0.9;
  }
  return data.price;
}

嵌入运行(Go / C):

JSValue fn = JS_Eval(ctx, script, strlen(script), "<form>", JS_EVAL_TYPE_GLOBAL);
JSValue arg = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, arg, "price", JS_NewFloat64(ctx, 100.0));
JS_SetPropertyStr(ctx, arg, "type", JS_NewString(ctx, "vip"));

JSValue result = JS_Call(ctx, fn, JS_UNDEFINED, 1, &arg);
double val; JS_ToFloat64(ctx, &val, result);
printf("Result: %.2f\n", val);

输出:

Result: 90.00

9.4 示例:策略与风控规则引擎

function shouldBlock(user) {
  if (user.age < 18 || user.region === "blacklist") return true;
  if (user.score < 50) return true;
  return false;
}

宿主系统:

if (JS_EvalFunction(ctx, rule_func, arg) == JS_TRUE)
    block_request(user);

这种模式非常适合“动态风控 / 广告投放 / 游戏 AI 行为脚本”。

9.5 QuickJS + Redis + HTTP 桥接

QuickJS 可结合 Redis 与 HTTP 形成规则集执行框架。

flowchart TB
    A["HTTP 事件"] --> B["QuickJS Runtime"]
    B --> C["脚本规则执行"]
    C --> D["Redis 存储"]
    C --> E["HTTP 回调"]

注册 C 函数:

register_function(ctx, "httpPost", js_http_post);
register_function(ctx, "redisSet", js_redis_set);

用户脚本:

redisSet("user:1", "active");
httpPost("/notify", { id: 1 });

9.6 低代码与自动化平台中的 QuickJS

QuickJS 可以作为“逻辑执行节点”,支持可视化编排系统执行用户逻辑:

graph LR
    A["拖拽式节点编辑器"] --> B["JS 脚本节点"]
    B --> C["QuickJS Sandbox"]
    C --> D["执行结果 / 输出"]

代码示例:

function onNode(input) {
  return input.x + input.y;
}

在 C 层注册:

JS_Eval(ctx, script, strlen(script), "<node>", JS_EVAL_TYPE_GLOBAL);

9.7 QuickJS 与 WebAssembly (WASM) 集成

QuickJS 可以与 WASM 模块混合运行,实现跨语言逻辑层。

flowchart TB
    A["QuickJS Runtime"] --> B["WASM 模块调用"]
    B --> C["C 函数导出"]
    A --> D["JS 逻辑控制"]

这为 SaaS 提供了:

  • JS 的灵活性;
  • WASM 的性能;
  • C 层的安全沙箱。

9.8 QuickJS 在 AI 与 Prompt 平台中的应用

例如在 AI 工具链中动态生成请求参数:

function buildPrompt(user, input) {
  return `${user.name} said: ${input.toUpperCase()}`;
}

QuickJS 可在云端执行用户定义脚本,安全拼装参数,防止注入风险。

9.9 多租户隔离方案

级别实现方式优点缺点
Context 隔离每租户一个 Context快速、轻量内存共享有限
Runtime 隔离每租户独立 Runtime安全、强隔离内存消耗高
进程隔离每租户独立进程最高安全性启动慢

推荐模式:

SaaS 小规模:多 Context
高安全系统:多 Runtime
云原生方案:容器级隔离

9.10 SaaS 性能与稳定性优化策略

优化点建议
脚本编译缓存JS_ReadObject / JS_WriteObject
JobQueue 循环统一事件循环驱动
Context 复用避免频繁创建销毁
GC 调度周期性回收
异常隔离每 Context 捕获异常
指令限流自定义 instruction_limit
沙箱注入禁止导入 osstd 模块

9.11 SaaS 集成架构(详细)

flowchart TB
    A["HTTP Gateway"]
    B["Script Dispatcher"]
    C["QuickJS Sandbox Pool"]
    D["Result Aggregator"]
    E["DB / Cache"]
    A --> B --> C --> D --> E
    C -->|GC| C
    B -->|Load Balance| C

9.12 QuickJS 的业务可扩展模式

  • 插件化架构:每个插件一个 JS 模块;
  • 热更新机制:通过字节码更新逻辑;
  • 审计系统:记录每次脚本执行日志;
  • 安全策略:沙箱封闭环境;
  • 统一配置中心:存储脚本版本与依赖。

9.13 小结

QuickJS 在 SaaS 场景中可实现:

  • 动态逻辑;
  • 用户可编程;
  • 沙箱安全;
  • 热更新与日志追踪;
  • 高度可扩展的执行层。

第 10 章:性能优化与安全沙箱设计

10.1 性能调优总览

QuickJS 的性能取决于以下因素:

因素影响优化方向
字节码编译启动速度使用 qjsc 预编译
跨语言调用函数开销合并调用逻辑
GC 周期内存抖动调整频率
字符串池内存利用复用 Atom
Context 数量并行能力控制池大小

10.2 脚本预编译与缓存

JSValue obj = JS_ReadObject(ctx, buffer, len, JS_READ_OBJ_BYTECODE);
JSValue val = JS_EvalFunction(ctx, obj);

将脚本编译为字节码后持久化存储,大幅减少启动开销。

10.3 GC 调度优化

建议采用:

  • 定时触发(如每 10 秒一次);
  • 内存阈值触发
  • 手动触发关键节点

示例:

if (used_memory > 20*1024*1024) JS_RunGC(rt);

10.4 JobQueue 调度优化

为异步任务建立统一执行循环:

while (JS_IsJobPending(rt)) {
    JS_ExecutePendingJob(rt, &ctx);
}

可集成到 epoll 或主循环中,提升 async 任务吞吐。

10.5 跨语言通信优化

  • 减少函数调用频率;
  • 批量传输数据(如数组或 JSON);
  • 使用 C 层缓冲区复用。

示例:

JSValue json = JS_Eval(ctx, "JSON.stringify(obj)", -1, "<eval>", 0);

10.6 沙箱安全体系

QuickJS 本身不会访问系统资源,但默认模块(如 os, std)存在安全隐患。

禁用标准模块

#define CONFIG_NO_OS_MODULE
#define CONFIG_NO_STD_MODULE

封闭模块加载器

JS_SetModuleLoaderFunc(rt, NULL, safe_loader, NULL);

限制内存与栈

JS_SetMemoryLimit(rt, 32 * 1024 * 1024);
JS_SetMaxStackSize(rt, 1 << 18);

禁止无限循环

JS_SetInterruptHandler(rt, interrupt_cb, NULL);

示例:

static int interrupt_cb(JSRuntime *rt, void *opaque) {
    return should_stop() ? 1 : 0;
}

10.7 沙箱隔离层架构

flowchart TB
    A["Host Process"]
    B["QuickJS Runtime"]
    C["Context 1"]
    D["Context 2"]
    E["API Proxy Layer"]
    F["Safe C Modules"]
    A --> B --> C
    B --> D
    C --> E --> F

10.8 安全白名单策略

层级控制目标实现方式
模块层禁止导入自定义 loader
函数层仅允许安全 API注册白名单
数据层禁止访问敏感数据参数过滤
执行层防止死循环中断回调
内存层限制堆栈运行时约束

10.9 审计与日志系统

QuickJS 可通过:

  • 记录每次执行脚本;
  • 统计执行时间;
  • 记录异常堆栈;
  • 输出 GC 统计。
printf("[QJS] Script took %.2fms\n", exec_time);

或:

try {
  runTask();
} catch (e) {
  log("error", e.stack);
}

10.10 防御策略总结

风险解决方式
无限循环中断回调
内存暴涨内存上限
非法模块禁止导入
文件访问移除 os 模块
恶意脚本签名校验
数据泄露沙箱 API 白名单
调用阻塞JobQueue 管理

10.11 性能与安全平衡

QuickJS 的设计理念:

“默认安全,按需开放。”

建议策略:

  1. 默认禁用所有外部模块;
  2. 按需注册 API;
  3. 对脚本执行设置时间/内存/栈限制;
  4. 所有异常记录与上报。

10.12 监控与指标体系

指标描述
ctx_count当前上下文数量
mem_usage运行时内存占用
gc_time每次 GC 延迟
exec_time脚本执行时间
job_pending异步任务数量

可导出至 Prometheus / Grafana。

10.13 工业级部署架构

flowchart LR
    A["Load Balancer"]
    B["Worker Pool"]
    C["QuickJS Runtime Pool"]
    D["Context Manager"]
    E["Sandbox Monitor"]
    F["Persistent Storage"]

    A --> B --> C --> D --> F
    D --> E
    E --> C

特点:

  • 可水平扩展;
  • 可监控;
  • 可快速回收;
  • 安全隔离。

10.14 未来趋势:QuickJS + WASM + AI

结合 WebAssembly 与 AI 推理框架,QuickJS 正逐渐演变为:

  • 云端轻量执行层;
  • 边缘智能脚本层;
  • 低功耗可控逻辑引擎。

应用场景包括:

  • AI 规则解释器;
  • 云函数运行时;
  • 智能控制脚本;
  • 自动化工作流引擎。

10.15 小结

维度优化要点
性能预编译、Context 复用、GC 调度
安全沙箱隔离、白名单、资源限制
可维护性模块化、可观测、热更新
可扩展性多语言绑定、容器化运行
未来方向与 WASM / AI 融合

全书总结

QuickJS 的价值在于:嵌入容易;性能高效;标准兼容;安全可控;可跨语言生态集成。

它既是 轻量引擎,也是 脚本操作系统的基石

从 IoT 到 SaaS,从游戏到云函数,QuickJS 已成为连接 嵌入式世界与云端智能 的关键桥梁。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页