MicroQuickJS在10KB内存跑JavaScript:嵌入式设备平滑运行JS!


MicroQuickJS是由法布里斯·贝拉打造的超轻量JavaScript引擎,仅需10KB内存即可运行,专为极端资源受限的嵌入式设备设计,采用严格ES5子集、跟踪式GC和ROM化标准库,实现极致效率与安全。


你以为只有Node.js和V8能跑JS?这个10KB内存的“袖珍引擎”正在颠覆嵌入式世界

将来在你手里的智能手表、家里的温控器、甚至工厂里的传感器芯片上,可能正在运行一段JavaScript代码。但别误会,那绝不是你熟悉的Node.js,也不是浏览器里的V8引擎——而是一个叫MicroQuickJS(微型QuickJS)的超轻量级JavaScript引擎。

它的创造者是谁?正是那个让技术圈集体膜拜的“神级程序员”法布里斯·贝拉(Fabrice Bellard)!这位传奇人物不仅是FFmpeg、QEMU、Tiny C Compiler的作者,还曾用一台普通PC刷新圆周率计算世界纪录。

如今,他再次出手,把现代脚本语言塞进仅有10KB内存的嵌入式设备里,彻底打破“JS只能跑在大内存环境”的认知边界。

超越极限:10KB内存也能跑JavaScript?这不是科幻,是MicroQuickJS的日常

想象一下,你的程序要在一块连动态内存分配都不支持的微控制器上运行,RAM只有10KB,ROM不到100KB,没有操作系统,没有标准库,连malloc都用不了。在这种环境下,别说Python,就连C语言都要精打细算。

但MicroQuickJS却说:“没问题,JavaScript也能上!”这并不是营销话术,而是实实在在的工程奇迹。

根据官方GitHub数据,MQuickJS在ARM Thumb-2架构下,整个引擎加上C标准库仅需约100KB ROM空间,而运行时的RAM占用最低可压到10KB——这意味着它能跑在STM32F1、ESP32甚至更古老的8/16位MCU上。

更惊人的是,它不是“阉割版解释器”,而是具备完整词法分析、语法解析、字节码编译和垃圾回收能力的真正JavaScript引擎。你可以在命令行里这样测试它:./mqjs --memory-limit 10k tests/mandelbrot.js,直接限制内存为10KB来跑曼德勃罗集计算,而它真的能跑完!这种“在螺丝壳里做道场”的能力,正是MQuickJS最震撼的地方。

严格模式不是选项,而是默认:为嵌入式安全与效率而生的JS子集

很多前端开发者听到“严格模式”就头疼,觉得限制太多、写起来束手束脚。

但在嵌入式世界,恰恰是这些“限制”保障了系统的稳定与可预测性。MQuickJS从设计之初就只支持严格模式(strict mode)下的ES5子集,这意味着它直接禁用了with语句、隐式全局变量、稀疏数组等容易引发内存泄漏或逻辑错误的特性。

比如,你不能这样写:a = []; a[10] = 2; 因为这会在数组中间留下“空洞”,MQuickJS会直接抛出TypeError。
如果你想模拟稀疏结构,官方建议你改用普通对象:a = {}; a[0] = 1; a[10] = 2;。
再比如,eval函数只支持间接调用——(1, eval)('1 + 2')是合法的,但直接eval('1 + 2')会被禁止,因为后者能访问局部作用域,破坏封装性。

这些看似“倒退”的设计,实则是为了在资源受限环境下避免不可控的内存爆炸和执行路径混乱。MQuickJS的理念很明确:宁可牺牲一点语法灵活性,也要确保每一字节内存都用在刀刃上,每一次执行都可预测、可追踪。

垃圾回收不再依赖引用计数:跟踪式GC如何让对象“自由搬家”

传统嵌入式JS引擎(包括早期QuickJS)多采用引用计数来管理内存,虽然简单但存在循环引用无法释放的致命缺陷。而MQuickJS大胆采用了跟踪式垃圾回收器(tracing garbage collector),不仅能自动清理循环引用,还支持内存压缩(compacting),彻底避免内存碎片化。

但这带来一个新问题:对象在内存中的地址会随着GC运行而“搬家”。为了解决这个问题,MQuickJS的C API设计了一套独特的引用机制。你不能直接把JSValue变量长期保存在C栈上,而必须通过JS_PushGCRef()获取一个“受保护的引用指针”,这个指针会在对象移动时自动更新。看这段官方示例代码就明白了:

JSValue my_js_func(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv)
{
        JSGCRef obj1_ref, obj2_ref;
        JSValue *obj1, *obj2, ret;
        ret = JS_EXCEPTION;
        obj1 = JS_PushGCRef(ctx, &obj1_ref);
        obj2 = JS_PushGCRef(ctx, &obj2_ref);
        *obj1 = JS_NewObject(ctx);
        if (JS_IsException(*obj1))
            goto fail;
        *obj2 = JS_NewObject(ctx); // obj1可能在此时被GC移动
        if (JS_IsException(*obj2))
            goto fail;
        JS_SetPropertyStr(ctx, *obj1, "x", *obj2);  // obj1和obj2都可能移动
        ret = *obj1;
     fail:
        PopGCRef(ctx, &obj2_ref);
        PopGCRef(ctx, &obj1_ref);
        return ret;
}

这种设计虽然增加了C层开发的复杂度,却换来了极致的内存效率——每个JS对象仅需3个CPU字(32位系统下12字节),属性表也经过高度优化。正是这种“用开发复杂度换运行效率”的哲学,让MQuickJS在10KB内存里也能玩转对象、函数和闭包。

字节码可固化到ROM:让JS脚本像固件一样“烧录”进芯片

在嵌入式开发中,代码往往需要固化到只读存储器(ROM)中,以节省宝贵的RAM并提高启动速度。MQuickJS完美支持这一场景。

你可以先在PC上把JS源码编译成字节码:./mqjs -o mandelbrot.bin tests/mandelbrot.js,然后将这个.mandelbrot.bin文件直接烧录进设备的Flash。运行时,设备只需调用JS_LoadBytecode()加载该字节码,再用JS_Run()执行即可。

更贴心的是,MQuickJS提供了JS_RelocateBytecode()函数,用于在烧录前对字节码进行重定位,确保它能在目标设备的固定地址空间正确运行。这意味着你的JS逻辑可以像C固件一样,成为设备不可分割的一部分。

对于需要频繁更新业务逻辑但又无法支持OTA的低端设备来说,这简直是神技——只需更换一小段字节码,就能改变设备行为,而无需重刷整个固件。

极简标准库:全ROM化设计,启动快如闪电

你可能会担心:这么小的引擎,标准库是不是残缺不全?
MQuickJS的答案是:功能精简但高度优化。

它的标准库(如Math、Array、String等)并非运行时动态构建,而是在编译阶段就被mquickjs_build.c工具转成C结构体,直接嵌入到ROM中。

因此,当你创建一个JS上下文时,标准库几乎是“零成本”加载——不需要在RAM中新建任何对象,所有函数和原型都指向ROM中的只读数据。这不仅极大节省了内存,还让引擎初始化速度飞快。

举个例子,Date对象只支持Date.now(),因为完整的时间解析在嵌入式环境下太奢侈;String的toLowerCase/toUpperCase只处理ASCII字符,因为全Unicode转换需要庞大的映射表;RegExp的大小写折叠也仅限ASCII。但与此同时,它又悄悄塞进了一些现代特性:for of循环(仅限数组)、TypedArray、Math.imul、String.codePointAt、replaceAll等。这种“该省则省,该给就给”的策略,精准击中了嵌入式开发者的痛点。

作者传奇:法布里斯·贝拉——那个用一台PC打破超级计算机纪录的男人

谈到MQuickJS,就不得不提它的灵魂人物——法布里斯·贝拉(Fabrice Bellard)。这位法国程序员堪称“现实版托尼·斯塔克”,他的作品几乎定义了开源世界的多个领域。

2000年,他发布FFmpeg,成为几乎所有音视频软件的底层引擎;
2005年,他写出QEMU,让虚拟化技术走向大众;
2009年,他用一台普通PC和自己写的程序,以2.7万亿位精度计算圆周率,打破吉尼斯世界纪录,而当时超算纪录才2.6万亿。
2019年,他又推出QuickJS,一个支持最新ECMAScript标准、可嵌入、带JIT编译的JS引擎,震惊业界。

如今,他再次聚焦“极简”——不是为了炫技,而是真正思考:在算力爆炸的时代,我们是否忽略了那些沉默的“小设备”?MQuickJS正是他对“技术普惠性”的又一次回应:让最底层的芯片也能拥有脚本能力,让JavaScript真正无处不在。

与QuickJS的关系:不是替代,而是“特种兵”与“常规军”的分工

很多人会问:既然有QuickJS,为什么还要MQuickJS?答案很简单:它们服务的战场完全不同。

QuickJS是“常规军”,目标是通用嵌入式、桌面脚本、教学工具等场景,支持ES2023大部分特性,内存占用在100KB以上,性能接近V8但无需庞大依赖。
而MQuickJS是“特种兵”,专攻RAM低于32KB、ROM小于256KB的极端环境,比如工业传感器、电池供电的IoT节点、汽车ECU等。它牺牲了语言兼容性和动态特性,换取了极致的资源效率。

你可以把QuickJS看作“能跑在树莓派上的JS引擎”,而MQuickJS则是“能跑在一块纽扣电池驱动的芯片上的JS引擎”。

两者同源(共享部分解析器代码),但内核设计迥异:QuickJS用引用计数,MQuickJS用跟踪GC;QuickJS字符串用UTF-16/8混合存储,MQuickJS统一UTF-8;QuickJS支持完整eval和with,MQuickJS直接禁用。这种“一母双子,各司其职”的架构,展现了贝拉对技术分层的深刻理解。

开发者如何上手?从命令行到C嵌入的完整路径

想试试MQuickJS?官方提供了极其友好的入门路径。

首先克隆仓库:git clone https://github.com/bellard/mquickjs.git,然后make即可编译出mqjs可执行文件。你可以直接运行JS脚本:./mqjs hello.js,也可以进入交互模式:./mqjs -i。

更酷的是,你可以限制内存:./mqjs --memory-limit 10k script.js,亲眼见证它在10KB内存里挣扎求生(并成功)。

如果你是嵌入式开发者,重点看example.c和mqjs_stdlib.c。
前者展示了如何在C程序中创建JS上下文、传入内存缓冲区、调用JS函数;
后者则是标准库的ROM化模板。你只需提供一个静态内存块,比如uint8_t mem_buf[8192]; 然后调用JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib); 即可启动引擎。

整个过程不依赖malloc、不调用printf,完全自包含。对于ARM Cortex-M系列开发者,官方还提供了Thumb-2优化,确保代码密度最高。可以说,只要你会写C,就能把JS能力嵌入到任何设备中。

MQuickJS 重塑智能硬件的开发范式

,未来的智能灯泡不再只是“开/关/调色”,而是能运行用户上传的JS脚本来实现“日出模拟”“音乐同步闪烁”甚至“节日特效”。

而这一切,不需要Wi-Fi模块,不需要Linux系统,只需要一块成本几块钱的MCU和MQuickJS引擎。这正是MQuickJS的颠覆性所在:它把脚本能力下沉到硬件最底层,让设备从“功能固化”走向“逻辑可编程”。

对于厂商来说,这意味着固件升级不再需要召回设备,只需推送一段JS字节码;对于开发者来说,这意味着可以用熟悉的JavaScript快速原型验证,再无缝部署到量产硬件。

更深远的是,它为“边缘智能”提供了新思路——在传感器端直接运行轻量级AI推理脚本(配合TinyML),而非把所有数据传到云端。MQuickJS或许不会出现在你的手机或电脑里,但它很可能正在你家的某个角落,默默执行着一段10KB内存里的JavaScript,让世界变得更智能一点。