
Momentum Firmware JS 引擎数据类型详解mJS 的 string、number、foreign、ArrayBuffer 与 DataView 全解析【免费下载链接】Momentum-Firmware Feature-rich, stable and customizable Flipper Firmware项目地址: https://gitcode.com/GitHub_Trending/mo/Momentum-FirmwaremJS 是内置于 Momentum Firmware基于 Flipper Zero 的固件中的轻量级 JavaScript 引擎用于运行用户编写的 JS 脚本应用.js。本文以 documentation/js/js_data_types.md 为核心逐一剖析 mJS 支持的 10 类数据类型并结合 lib/mjs 下的引擎源码与 applications/system/js_app/examples/apps/Scripts 中的真实示例讲清每种类型的语义、底层表示与实战用法。读完本文你将能准确判断在固件脚本中何时用 Object、何时用 Array、如何用 ArrayBuffer/DataView 做二进制数据处理并理解 foreign 指针类型在 JS 与 C 边界间的桥梁作用。一、mJS 数据类型总览原文档给出的 mJS 常用数据类型清单如下数据类型说明string单字节字符序列不支持 UTF8number数值boolean布尔值foreignC 函数或数据指针undefined未定义值null空值Object带命名字段的数据结构Array特殊类型对象所有项都有索引且类型相等ArrayBuffer原始数据缓冲区DataView提供访问 ArrayBuffer 内容的接口从引擎内部视角看这些类型被划分为原始类型与Object 的类别两大类。在 lib/mjs/mjs_core_public.h 的enum mjs_type中可以看到引擎对类型的官方分组原始类型Primitive typesMJS_TYPE_UNDEFINED、MJS_TYPE_NULL、MJS_TYPE_BOOLEAN、MJS_TYPE_NUMBER、MJS_TYPE_STRING、MJS_TYPE_FOREIGN、MJS_TYPE_ARRAY_BUF、MJS_TYPE_ARRAY_BUF_VIEWObject 的不同类别MJS_TYPE_OBJECT_GENERIC普通对象、MJS_TYPE_OBJECT_ARRAY数组、MJS_TYPE_OBJECT_FUNCTION函数值得注意ArrayBuffer与DataView在枚举中归属于原始类型分组而Array与Function属于 Object 类别——这与 ECMAScript 中函数和数组都是对象的认知一致也解释了为何 mJS 中Array的每一项都要求拥有索引、且类型相等。二、string单字节字符序列无 UTF-8mJS 的字符串是单字节字符序列每个字符恰好占 1 个字节不提供 UTF-8 多字节编码支持。这意味着中文等多字节字符无法被 mJS 字符串直接正确表示开发脚本时应避免在字符串中嵌入非 ASCII 字符。引擎内部为字符串实现了精细的分级存储策略。在 lib/mjs/mjs_core_public.h 中定义了多种字符串标签MJS_TAG_STRING_I内联字符串长度 5 字节直接嵌入值中零额外分配MJS_TAG_STRING_5内联字符串长度恰好 5 字节MJS_TAG_STRING_O自有字符串owned string引擎持有其内存所有权MJS_TAG_STRING_F外来字符串foreign string指向外部数据不拷贝MJS_TAG_STRING_C字符串分片chunkMJS_TAG_STRING_D字典字符串dictionary string用于属性名等可共享的短串这种短串内联、长串外存的设计对应 lib/mjs/mjs_string.h 中embed_string等实现是 mJS 面向内存受限嵌入式环境的核心优化5 字节以内的短字符串不产生堆分配避免频繁触发垃圾回收。mJS 为字符串提供了toLowerCase、toUpperCase、slice、indexOf、charCodeAt等方法见 lib/mjs/mjs_string.h日常脚本处理文本足够使用。三、numberIEEE 754 双精度浮点与 NaN-packingmJS 的number遵循 IEEE 754 双精度浮点格式8 字节1 位符号位、11 位指数、52 位尾数。与标准 JavaScript 一致NaN、Infinity等特殊值同样存在。mJS 在底层实现上采用了著名的NaN-packing技术既然NaN的指数位全为 1mJS 就借用NaN的载荷区来打包所有 JS 值。在 lib/mjs/mjs_core_public.h 中有完整说明11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv NaN marker |type| 48-bit placeholder for values: pointers, strings前 12 位固定为0xfffNaN 标记4 位type标签标识值的具体类型低 48 位存放实际载荷指针、字符串偏移等typedef uint64_t mjs_val_t;定义了引擎中所有 JS 值的统一表示lib/mjs/mjs_core_public.h。由于 64 位平台上指针实际只有 48 位有效地址空间指针可以安全塞入载荷区。标签定义见 lib/mjs/mjs_core_public.h共使用 32 种可能标签中的一部分例如MJS_TAG_OBJECT、MJS_TAG_FOREIGN、MJS_TAG_UNDEFINED、MJS_TAG_BOOLEANMJS_TAG_ARRAY、MJS_TAG_FUNCTION、MJS_TAG_NULLMJS_TAG_ARRAY_BUFArrayBuffer、MJS_TAG_ARRAY_BUF_VIEWDataViewC 侧创建与读取数字的 API 为mjs_mk_number()与mjs_get_double()、mjs_get_int()、mjs_get_int32()见 lib/mjs/mjs_primitive_public.h。其中mjs_get_int()会丢弃小数部分而mjs_get_int32()保证返回 32 位有符号整数——在固件脚本中做位运算或与硬件寄存器打交道时建议使用后者语义一致的写法。四、boolean、undefined 与 null这三个是 mJS 的基础标量类型语义与标准 JavaScript 一致boolean只有true和false两个值。C 侧通过mjs_mk_boolean()创建、mjs_get_bool()读取、mjs_is_boolean()判断lib/mjs/mjs_primitive_public.h。undefined表示未定义通常是未初始化变量或函数未返回值。对应宏MJS_UNDEFINEDlib/mjs/mjs_primitive_public.h。null表示空值对应宏MJS_NULL。注意 mJS 保留了mjs_mk_null()兼容接口但官方注释已标记其废弃推荐直接使用MJS_NULL宏lib/mjs/mjs_primitive_public.h。在 ArrayBuffer/DataView 的读写边界上undefined还被用作越界哨兵例如 DataView 按索引取值越界时mjs_dataview_get()会返回MJS_UNDEFINED见 lib/mjs/mjs_array_buf.c这与标准 JS 中越界读取返回undefined的行为保持一致。五、foreignC 函数与数据指针的桥接foreign是 mJS 中最具嵌入式特色的类型它表示一个C 函数指针或 C 数据指针。其创建 API 有两个mjs_mk_foreign(mjs, ptr)打包数据指针lib/mjs/mjs_primitive_public.hmjs_mk_foreign_func(mjs, fn)打包函数指针lib/mjs/mjs_primitive_public.h宏MJS_MK_FN(fn)是其便捷写法读取侧对应mjs_get_ptr()判断侧为mjs_is_foreign()lib/mjs/mjs_primitive_public.h。原文档明确指出 foreign 的语义边界JS 代码不能对 foreign 值做任何有用的操作只能在属性中持有它并传来传去它表现得像一个没有属性的密封对象。这正是 foreign 的设计初衷——它纯粹是 C 侧注册的 JS 内置函数如print、delay等在引擎内部的载体JS 脚本一般不会直接接触到 foreign 值。源码中还给出了一个重要的存储告诫由于 foreign 依赖 48 位地址空间在需要存放正好sizeof(void*)字节、且sizeof(void*) 8的原始数据时不要用 foreign请改用字节数组ArrayBufferlib/mjs/mjs_primitive_public.h。这条建议直接指向下一节的主角。六、Object 与 Array数据结构的两类载体Object带命名字段的数据结构对应MJS_TYPE_OBJECT_GENERIC。脚本中用字面量{a: 1, b: x}创建C 侧用mjs_mk_object()、mjs_set()、mjs_get()操作。字段名通常走字典字符串MJS_TAG_STRING_D路径重复出现的属性名可共享同一份存储。ArrayMJS_TYPE_OBJECT_ARRAY。原文档强调其特殊性——所有项都有索引且类型相等。这意味着 mJS 的数组在实现上比标准 JS 数组更严格、也更紧凑适合存放同构数据序列例如一组温度读数、一组 RGB 值。C 侧 API 见 lib/mjs/mjs_array.h 与mjs_array_public.h支持mjs_array_get、mjs_array_push_internalpush、mjs_array_splicesplice等常规操作。需要留意正因数组要求元素类型相等混入不同类型元素的写法在 mJS 中可能触发类型错误编写脚本时应尽量保证数组元素的同质性。七、ArrayBuffer原始二进制缓冲区ArrayBuffer是 mJS 提供的原始数据缓冲区对应标签MJS_TAG_ARRAY_BUFlib/mjs/mjs_core_public.h类型为MJS_TYPE_ARRAY_BUF。它不与任何具体数据类型绑定只是若干字节的连续内存。在引擎内部所有 ArrayBuffer 的数据存放在一个统一管理的mbuf动态字节缓冲中mjs-array_buffers每个缓冲区前有一个 varint 编码的长度头。创建与读取的 C API 为mjs_mk_array_buf()与mjs_array_buf_get_ptr()lib/mjs/mjs_array_buf_public.h。为避免频繁扩容每次分配还会多预留MJS_ARRAY_BUF_RESERVE默认 100字节lib/mjs/mjs_array_buf.c。mJS 为 ArrayBuffer 提供了slice(start, end)方法校验参数个数0~2 个、起始/结束位置合法性后从源缓冲区切出一段并返回新的 ArrayBuffer实现见 lib/mjs/mjs_array_buf.c。仓库自带的示例脚本完整演示了这一用法let arr_1 Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); print(len , arr_1.buffer.byteLength); let arr_2 Uint8Array(arr_1.buffer.slice(2, 6)); print(slice len , arr_2.buffer.byteLength); for (let i 0; i arr_2.buffer.byteLength; i) { print(arr_2[i]); }完整文件见 array_buf_test.js八、DataView类型化视图与类型化数组DataView标签MJS_TAG_ARRAY_BUF_VIEW提供访问 ArrayBuffer 内容的类型化接口对应原文档所述provides interface for accessing ArrayBuffer contents。通过它可以把同一段缓冲区解释为不同宽度的整数序列。引擎内置了 6 种元素类型定义在 lib/mjs/mjs_array_buf_public.h 的mjs_dataview_type_t中类型元素宽度说明MJS_DATAVIEW_U81 字节无符号 8 位整数MJS_DATAVIEW_I81 字节有符号 8 位整数MJS_DATAVIEW_U162 字节无符号 16 位整数MJS_DATAVIEW_I162 字节有符号 16 位整数MJS_DATAVIEW_U324 字节无符号 32 位整数MJS_DATAVIEW_I324 字节有符号 32 位整数元素宽度映射在mjs_dataview_get_element_len()中实现lib/mjs/mjs_array_buf.c。这 6 种视图在 JS 全局作用域下对应构造器Uint8Array、Int8Array、Uint16Array、Int16Array、Uint32Array、Int32Array注册于mjs_init_builtin_array_buf()lib/mjs/mjs_array_buf.c。DataView构造器支持三种入参形式见 lib/mjs/mjs_array_buf.c传入现有 ArrayBuffer在已有缓冲区上建立视图mjs_mk_dataview_from_buf传入数字长度自动分配等长的新 ArrayBuffer传入普通数组按元素类型逐个拷贝数组元素到新缓冲区。读取与写入分别由mjs_dataview_get()/mjs_dataview_set()完成且二者都会做越界检查——按索引读取越界返回undefined写入越界则返回MJS_TYPE_ERRORlib/mjs/mjs_array_buf.c。视图内部通过_t字段记录元素类型、buffer字段引用底层 ArrayBufferlib/mjs/mjs_array_buf.c。此外从 ArrayBuffer 建立视图时要求缓冲区长度必须是元素宽度的整数倍否则报MJS_BAD_ARGS_ERROR。九、在 Flipper 脚本中的实战模式官方文档在 documentation/js/ReadMe.md 中特别提示documentation/js目录下的说明为手工维护可能存在滞后权威依据是 TypeScript 类型定义applications/system/js_app/packages/fz-sdk下的.d.ts文件与applications/system/js_app/examples/apps/Scripts下的示例脚本。结合示例可以看到 ArrayBuffer/Uint8Array 在固件脚本中的三类典型用途1. 外设收发缓冲i2c/spi在 i2c.js 与 spi.js 中读写数据均以Uint8Array(data_buf)形式构造缓冲注释明确写道也可以直接传Uint8Array([0x00, 0x00, ...])作为写参数即字节数组既是写入参数也是读取结果的载体。2. 串口数据视图uartuart_echo.js 中let data_view Uint8Array(rx_data);将收到的原始字节流包装为类型化视图再逐元素访问——这正是 DataView 语义读写 ArrayBuffer 内容的接口的直接体现。3. BLE 广播包构造blebeaconblebeacon.js 中blebeacon.setData(Uint8Array(packet))用Uint8Array打包广播数据对应类型定义 blebeacon/index.d.ts 中setData(data: Uint8Array)的签名。此外 gui.js 中defaultData: Uint8Array([0x11, 0x22, ...])表明 GUI 组件的字节属性同样接受类型化数组。4. 存储 API 的缓冲参数在 C 侧模块 js_storage.c 中参数校验信息显示存储接口的写参数expected string or ArrayBuffer——即ArrayBuffer与string一样可作为文件写入的数据来源这说明 ArrayBuffer 已深度融入固件 JS API 的参数体系。十、总结mJS 的 10 类数据类型构成了 Flipper 脚本世界的完整类型体系string单字节、无 UTF-8与numberIEEE 754 双精度处理基础数据boolean/undefined/null表达逻辑与空值foreign在 JS 与 C 之间传递指针Object/Array组织结构化数据数组要求元素类型相等而ArrayBufferDataView组合则提供了面向外设通信的原始二进制处理能力。理解这套类型系统尤其是NaN-packing 统一表示所有值字符串短串内联ArrayBuffer 集中缓冲管理三个底层设计将帮助你在 Momentum Firmware 上写出更高效、更符合引擎预期的 JS 应用。进一步阅读引擎源码位于 lib/mjs类型化数组完整实现见 mjs_array_buf.c 与 mjs_array_buf_public.h脚本示例见 examples/apps/Scripts/Examples权威类型声明见 packages/fz-sdk。【免费下载链接】Momentum-Firmware Feature-rich, stable and customizable Flipper Firmware项目地址: https://gitcode.com/GitHub_Trending/mo/Momentum-Firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考