强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

QuickJS 嵌入式 JavaScript 引擎完全教程 / 01 - QuickJS 概述

QuickJS 概述

1.1 QuickJS 简介

QuickJS 是由法国程序员 Fabrice Bellard 于 2019 年 7 月发布的轻量级 JavaScript 引擎。Bellard 以开发 FFmpeg、QEMU、TinyCC 等传奇项目闻名,QuickJS 体现了他一贯的工程哲学:用最少的代码实现最多的功能,且不依赖外部库

核心特性一览

特性说明
完整 ES2023 支持包括 Proxy、Symbol、Promise、async/await、Module 等
零外部依赖仅需标准 C 库(libc)
极小体积x86_64 上约 210KB 静态编译
字节码编译支持预编译为字节码,加速加载
垃圾回收引用计数 + 循环检测
数学大数内置 BigInt 和 BigDecimal 支持
262 测试套件通过率超过 95%
// QuickJS 最小示例:在 C 中执行 JavaScript
#include "quickjs.h"
#include <stdio.h>

int main() {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);

    const char *code = "1 + 2";
    JSValue result = JS_Eval(ctx, code, strlen(code), "<input>", 0);

    if (!JS_IsException(result)) {
        printf("Result: %d\n", JS_VALUE_GET_INT(result));
    }

    JS_FreeValue(ctx, result);
    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
    return 0;
}

1.2 ES2023 特性支持

QuickJS 几乎完整实现了 ECMAScript 2023 规范。以下是主要特性的支持情况:

语言特性

ES 特性QuickJS说明
let / const块级作用域
箭头函数 (Arrow Function)() => {}
模板字符串 (Template Literal)`hello ${name}`
解构赋值 (Destructuring){ a, b } = obj
展开运算符 (Spread)...arr
class 语法包括私有字段 #field
Promise完整实现
async / await异步编程
Symbol包括 Well-known Symbols
Proxy / Reflect元编程
Map / Set / WeakMap / WeakSet集合类型
Iterator / Generatorfunction* / yield
for...of迭代协议
import / exportES Module
BigInt任意精度整数
BigDecimal任意精度十进制(Stage 3 提案)
?? (Nullish Coalescing)空值合并
?. (Optional Chaining)可选链
globalThis全局对象
Array.prototype.at()负索引访问
Object.hasOwn()ES2022 新增
Top-level await模块顶层 await
RegExp 命名捕获组(?<name>...)
Array.prototype.findLast()ES2023 新增
Array.prototype.findLastIndex()ES2023 新增
Object.groupBy() / Map.groupBy()⚠️ES2024 提案,未完全支持
TemporalStage 3 提案,未实现

内置对象

// QuickJS 支持的内置对象示例

// 1. Promise 与 async/await
async function fetchData() {
    const result = await Promise.resolve(42);
    return result * 2;
}

// 2. Proxy
const handler = {
    get(target, prop) {
        console.log(`Accessing ${String(prop)}`);
        return target[prop];
    }
};
const proxy = new Proxy({ x: 1, y: 2 }, handler);
console.log(proxy.x); // 输出: Accessing x \n 1

// 3. Generator
function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacci();
console.log(
    Array.from({ length: 10 }, () => fib.next().value)
); // [0,1,1,2,3,5,8,13,21,34]

// 4. BigInt
const big = 9007199254740993n;
console.log(big + 1n); // 9007199254740994n

// 5. ES2023 Array 方法
const arr = [1, 2, 3, 4, 5];
console.log(arr.findLast(x => x % 2 === 0)); // 4
console.log(arr.findLastIndex(x => x % 2 === 0)); // 3

QuickJS 独有的扩展

QuickJS 还提供了一些规范之外的实用扩展:

// 1. console.log(非标准,但广泛使用)
console.log("hello", 42, [1,2,3]);

// 2. print 函数(QuickJS 特有)
print("QuickJS says hello!");

// 3. 脚本参数(通过 scriptArgs)
// 运行: qjs script.js arg1 arg2
scriptArgs.forEach(arg => print(arg));

// 4. 文件读写(需要 os 和 std 模块)
import * as std from "std";
import * as os from "os";

const content = std.loadFile("data.txt");
if (content !== null) {
    print(content);
}

// 5. Worker(Web Worker 兼容 API)
const w = new Worker("worker.js");
w.postMessage({ data: 42 });

1.3 QuickJS 与 V8 的对比

V8 是 Google 开发的高性能 JavaScript 引擎,用于 Chrome 浏览器和 Node.js。两者的设计目标有本质差异。

架构对比

┌──────────────────────────────────────────────────────────┐
│                        V8 架构                           │
├──────────────────────────────────────────────────────────┤
│  源代码 → Parser → AST → Ignition(字节码解释器)          │
│                      ↓                                   │
│              Sparkplug(基线编译器)                        │
│                      ↓                                   │
│              Maglev(中层优化编译器)                       │
│                      ↓                                   │
│              TurboFan(优化 JIT 编译器)                    │
└──────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│                     QuickJS 架构                         │
├──────────────────────────────────────────────────────────┤
│  源代码 → Parser → AST → 字节码编译器 → 字节码           │
│                                              ↓           │
│                                    解释器循环执行         │
│                           (可选: 预编译为二进制字节码)     │
└──────────────────────────────────────────────────────────┘

详细对比表

维度QuickJSV8
编译策略解释执行(字节码)JIT 多层编译
执行速度较慢(约 V8 的 1/20-1/50)极快
启动速度极快较慢(JIT 预热)
内存占用极低(<1MB)高(10MB+)
二进制体积~210KB~10MB+
外部依赖ICU、zlib 等
适合场景嵌入式、轻量脚本桌面/服务端高性能
GC 策略引用计数 + 循环检测分代标记-清除
ES Module
WebAssembly❌(部分实验)
调试接口基础完整(DevTools)
平台支持几乎所有 C 编译器支持的平台主流桌面/移动

代码性能对比

// fibonacci.js — 典型的递归性能测试
function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

const start = Date.now();
const result = fib(35);
const elapsed = Date.now() - start;

console.log(`fib(35) = ${result}, time: ${elapsed}ms`);

典型运行时间对比(fib(35),单次运行):

引擎执行时间备注
QuickJS~3500ms解释执行
V8 (Node.js)~80msJIT 优化后
V8 (首次运行)~200msJIT 预热前
Duktape~8000msES5.1 引擎
Lua~1500msLuaJIT ~30ms

注意: QuickJS 的设计目标不是与 V8 竞争执行速度。在嵌入式场景中,启动速度、内存占用和二进制体积往往比运行速度更重要。


1.4 QuickJS 与 Duktape 的对比

Duktape 是另一个流行的嵌入式 JavaScript 引擎,两者经常被放在一起比较。

对比表

维度QuickJSDuktape
ES 规范版本ES2023(完整)ES5.1(几乎完整)
Promise
async/await
ES Module
class❌(ES5 模拟)
Symbol
Proxy
Generator
BigInt
二进制体积~210KB~300KB
性能较快较慢
维护状态活跃低活跃(2022年后少更新)
文档质量中等优秀
社区生态增长中成熟

选型建议

需要现代 JavaScript 特性?
├── 是 → QuickJS
│         ├── 需要 async/await? → QuickJS
│         ├── 需要 ES Module? → QuickJS
│         └── 需要 class? → QuickJS
└── 否(仅 ES5.1 即可)
          ├── 已有 Duktape 项目? → 继续使用 Duktape
          └── 新项目? → 推荐 QuickJS(更好的未来兼容性)

1.5 适用场景详解

场景 1:嵌入式设备脚本引擎

在 MCU(如 STM32、ESP32)上运行 JavaScript 配置脚本或业务逻辑。

// 在嵌入式设备上执行传感器数据处理脚本
const char *process_script = R"JS(
    function processData(rawValues) {
        const filtered = rawValues
            .filter(v => v > 0 && v < 4096)
            .map(v => (v / 4096) * 3.3);  // 转换为电压值
        
        const avg = filtered.reduce((a, b) => a + b, 0) / filtered.length;
        return {
            voltage: Math.round(avg * 1000) / 1000,
            count: filtered.length,
            status: avg > 2.5 ? 'HIGH' : 'NORMAL'
        };
    }
)JS";

场景 2:游戏脚本引擎

// 游戏 NPC 行为脚本
class NPCBehavior {
    constructor(npc) {
        this.npc = npc;
        this.state = 'idle';
        this.patrolPoints = [];
        this.currentTarget = 0;
    }

    update(dt) {
        switch (this.state) {
            case 'idle':
                this.idleBehavior(dt);
                break;
            case 'patrol':
                this.patrolBehavior(dt);
                break;
            case 'chase':
                this.chaseBehavior(dt);
                break;
        }
    }

    idleBehavior(dt) {
        // 检测玩家距离
        const dist = this.npc.distanceToPlayer();
        if (dist < 5.0) {
            this.state = 'chase';
        } else if (this.npc.idleTime > 3.0) {
            this.state = 'patrol';
        }
    }

    patrolBehavior(dt) {
        const target = this.patrolPoints[this.currentTarget];
        this.npc.moveToward(target, dt);

        if (this.npc.distanceTo(target) < 0.5) {
            this.currentTarget = 
                (this.currentTarget + 1) % this.patrolPoints.length;
        }
    }
}

场景 3:安全沙箱

// 用户提交的代码在沙箱中执行
// 限制:不能访问文件系统、网络、进程等
function untrustedUserCode(data) {
    // 用户可以自由使用 JavaScript 语法
    const result = data
        .filter(item => item.active)
        .map(item => ({
            id: item.id,
            score: item.value * 1.5,
            label: `${item.name} (${item.value})`
        }))
        .sort((a, b) => b.score - a.score);

    return result.slice(0, 10);
}

场景 4:配置语言

// 用 JavaScript 替代 JSON/TOML 作为配置语言
// 优势:支持注释、计算、条件逻辑
export default {
    server: {
        host: process.env.HOST || '0.0.0.0',
        port: parseInt(process.env.PORT) || 8080,
        workers: Math.max(1, os.cpus().length - 1),
    },
    database: {
        url: 'postgres://localhost/mydb',
        pool: { min: 2, max: 10 },
    },
    features: {
        // 支持注释!
        enableBeta: process.env.NODE_ENV === 'development',
        cacheTimeout: 60 * 60 * 24, // 可以计算
    }
};

1.6 QuickJS 的限制

在选择 QuickJS 之前,需要了解它的设计限制:

不适合的场景

场景原因推荐替代
高并发 Web 服务无 JIT,计算密集型任务慢Node.js (V8)、Deno
大型前端项目无 DOM API、无浏览器生态浏览器内建引擎
需要原生 WebAssembly仅部分实验性支持V8、SpiderMonkey
需要完整调试工具调试支持有限V8 (DevTools)

已知的规范兼容性缺口

// 以下在 QuickJS 中不可用或行为可能不同

// 1. Temporal API(未实现)
// const now = Temporal.Now.plainDateTimeISO();  // ❌

// 2. Atomics / SharedArrayBuffer(未实现)
// Atomics.store(buffer, 0, 42);  // ❌

// 3. WebAssembly(实验性,需编译时启用)
// const module = new WebAssembly.Module(bytes);  // ⚠️

// 4. FinalizationRegistry(未实现)
// const registry = new FinalizationRegistry(...);  // ❌

// 5. 性能敏感的正则表达式
// QuickJS 的正则引擎是回溯式的,某些模式可能慢于 V8

1.7 发展历史与社区

里程碑

日期事件
2019-07QuickJS 首次发布,支持 ES2019
2019-12添加 BigInt、BigDecimal、ES Module 支持
2020-03通过 Test262 75% 测试
2020-09性能优化,添加快速数组模式
2021-04支持 ES2021,通过 Test262 90%+
2022-09支持 ES2022,私有类字段
2023-03支持 ES2023,Array.findLast
2024-01社区维护的 GitHub 版本活跃更新
2024-06性能持续改进,新增尾调用优化
2025-01社区贡献的内存优化和平台扩展

相关项目生态

项目说明
quickjs-emscripten在浏览器/WebAssembly 中运行 QuickJS
go-quickjsGo 语言绑定
quickjs-rsRust 语言绑定
QuickJS-AndroidAndroid 平台集成
quickjsppC++ 封装库
liquidsoap使用 QuickJS 作为脚本引擎

1.8 本章小结

要点说明
QuickJS 是什么由 Fabrice Bellard 开发的小型 ES2023 JavaScript 引擎
核心优势极小体积、零依赖、完整 ES2023 支持
与 V8 区别不追求执行速度,专注于嵌入和轻量
与 Duktape 区别支持现代 JS 特性,更活跃的维护
最佳场景嵌入式、游戏脚本、安全沙箱、配置语言

扩展阅读