ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flipper Zero 的 mJS 脚本引擎数据类型详解:从基础类型到 ArrayBuffer/DataView

Flipper Zero 的 mJS 脚本引擎数据类型详解:从基础类型到 ArrayBuffer/DataView Flipper Zero 的 mJS 脚本引擎数据类型详解从基础类型到 ArrayBuffer/DataView【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware本文是 documentation/js 目录下《JS 数据类型》指南的深度展开面向在 Flipper Zero 上通过 JS 应用js_app 编写.js脚本的开发者。文章以官方文档列举的 9 类数据类型为主线逐类讲解其语义、边界与实战用法并深入 mJS 引擎lib/mjs源码揭示这些类型在嵌入式环境中的底层表示与设计取舍。读完后你将掌握 Flipper Zero JS 脚本中字符串、数字、布尔、foreign 指针、对象、数组及二进制缓冲区ArrayBuffer/DataView的完整用法并能依据底层行为写出内存友好、行为可预期的脚本。一、Flipper Zero 的 JS 运行时mJS 与数据类型的关系Flipper Zero 固件内置了 mJS——一个为嵌入式系统设计的极简 JavaScript 引擎其完整实现位于仓库 lib/mjs 目录下约 40 余个 C 源码文件包括 mjs_core.c、mjs_string.c、mjs_array_buf.c、mjs_dataview.c 等。在设备端JS 应用 负责加载并执行用户放置在 SD 卡上的.js脚本而脚本所能操纵的每一种值都属于 mJS 定义的数据类型。mJS 并不是完整 ES2015 实现而是一个够用且省内存的子集。这意味着它的数据类型在语义上接近 JavaScript但存在显著差异例如字符串不含 UTF-8 支持。理解这些类型是写出正确、高效、可移植脚本的前提。值得注意的是mJS 的所有值在 C 语言层面都统一封装为mjs_val_tmjs_core_public.h这是一个 64 位无符号整数。引擎利用 IEEE 754 double 的 NaN 表示来装箱不同数据类型高位 16 位作为类型标签tag低位 48 位存放实际负载指针、字符串或内联数据因此每一种 JS 值在内存中天然紧凑。从 mjs_core_public.h 可以看到全部标签定义例如MJS_TAG_OBJECT、MJS_TAG_ARRAY——对象与数组MJS_TAG_STRING_I / _5 / _O / _F / _C / _D——六种字符串表示MJS_TAG_FOREIGN——C 指针MJS_TAG_ARRAY_BUF、MJS_TAG_ARRAY_BUF_VIEW——ArrayBuffer 与 DataView。enum mjs_typemjs_core_public.h则给出了引擎视角下的类型枚举除MJS_TYPE_UNDEFINED / NULL / BOOLEAN / NUMBER / STRING / FOREIGN / ARRAY_BUF / ARRAY_BUF_VIEW等原始类型外对象还被细分为通用对象、数组、函数三类MJS_TYPE_OBJECT_GENERIC / _ARRAY / _FUNCTION。运行期判断值类型时mjs_get_type()会依据标签归类并返回上述枚举见 mjs_core.c。二、基础类型string、number、boolean、undefined、null原文档列出的第一组类型是脚本中最常用的基础类型下面逐一说明其语义与注意点。string单字节字符序列不支持 UTF-8本质由单字节字符组成的序列sequence of single byte characters。重要限制不支持 UTF-8。这意味着中文等多字节字符无法被正确处理脚本中的字符串应仅使用 ASCII 字符集。这一点在引擎实现中也得到印证mjs_string.c 将Rune字符简化为单个无符号字节读取chartorune每次只消费一个字节完全不解析多字节编码。底层优化字符串在mjs_val_t中并非只有一种存储方式。mjs_string.c 的mjs_mk_string()会根据长度与所有权copy标志选择不同策略长度 ≤ 4 的短字符串直接内联进值本身的负载区MJS_TAG_STRING_I长度为 5 时用MJS_TAG_STRING_5更长的字符串则放入引擎维护的字符串缓冲owned stringMJS_TAG_STRING_O或引用外部内存foreign stringMJS_TAG_STRING_F。这种分层设计正是为了让短字符串几乎不占用堆内存。对 C 互操作的意义mjs_get_string()返回的指针可能因 GC垃圾回收移动数据而失效因此 mjs_string_public.h 特别提示若需要长期持有字符串数据应使用拷贝语义创建mjs_mk_string(..., copy1)并通过mjs_get_cstring()获取 C 兼容的 NUL 结尾字符串。number双精度浮点数本质与标准 JavaScript 相同数字在内部一律以 IEEE 754双精度浮点数double表示覆盖整数与小数。位布局mjs_core_public.h 给出了完整布局1 位符号 11 位指数 52 位尾数其中指数全 1、尾数全 0 表示无穷大指数全 1、尾数最高位为 1 表示静默 NaN——而 mJS 正是借用这种 NaN 位模式来承载所有其他类型的标签与负载。C 侧转换引擎提供mjs_mk_number()、mjs_get_double()、mjs_get_int()、mjs_get_int32()等 APImjs_primitive_public.h。注意mjs_get_int()会直接丢弃小数部分且对非数字或 NaN 的结果未定义需要严格 32 位有符号整数时应使用mjs_get_int32()。boolean布尔值本质true/false由MJS_TAG_BOOLEAN标签标识负载中编码布尔状态。C 侧 APImjs_mk_boolean()/mjs_get_bool()/mjs_is_boolean()mjs_primitive_public.h。undefined 与 null两个空值本质undefined标签MJS_TAG_UNDEFINED表示未定义/未赋值null标签MJS_TAG_NULL表示显式的空对象。二者语义不同访问不存在的对象属性返回的是undefined而主动赋空应使用null。C 侧 API常量MJS_UNDEFINED、MJS_NULL及判断函数mjs_is_undefined()、mjs_is_null()mjs_primitive_public.h。典型场景在 Flipper Zero 脚本中检查返回值时常用if (x undefined)判断某次调用是否失败或属性缺失。三、foreign通往 C 世界的指针类型foreign是 mJS 区别于标准 JavaScript 的核心类型之一原文档将其定义为C 函数或数据指针foreign — C function or data pointer。本质一个在 JS 侧完全不透明的值用来封装来自 C 语言的void *指针或函数指针。JS 代码不能对 foreign 做任何有意义的内部分析只能持有并传递它行为类似于一个没有任何属性的密封对象。C 侧 APImjs_mk_foreign()数据指针、mjs_mk_foreign_func()函数指针、mjs_get_ptr()、mjs_is_foreign()mjs_primitive_public.h。位宽注意mjs_primitive_public.h 明确指出由于 foreign 值占用 48 位负载只有各平台实际可用的虚拟地址如 64 位架构上的 48 位地址空间中的指针才能被完整保留如果需要存储sizeof(void*) ≥ 8的原始字节官方建议改用字节数组byte array而非 foreign。在 Flipper Zero 中的角色JS SDK见 js_app/packages/fz-sdk/sdk.js通过 FFIForeign Function Interface见 mjs_ffi.c把 C 函数以 foreign 形式暴露给脚本开发者无需理解指针细节只需按 SDK 文档调用封装好的模块如furi.*、gpio.*、storage.*。四、Object 与 Array带字段的数据结构与索引集合Object命名字段的容器本质一个带命名字段的数据结构a data structure with named fields与标准 JS 的对象字面量一致例如let dev { name: flipper, rssi: -42 }; print(dev.name, dev.rssi);C 侧实现mjs_mk_object()创建空对象mjs_get()/mjs_set()/mjs_del()/mjs_next()完成属性读写与遍历mjs_object_public.h。此外 mJS 还提供mjs_struct_to_obj()可依据struct mjs_c_struct_member描述符把 C 结构体映射成 JS 对象供 FFI 返回复杂数据结构使用mjs_object_public.h。Array特殊类型的对象本质数组是对象的特例——所有元素都有索引并且类型一致all items have indexes and equal types。类型一致是 mJS 数组相对标准 JS 的一个重要约束向同一数组混入不同类型元素可能产生未定义行为或类型错误因此在 Flipper Zero 脚本中请保持数组元素类型统一。C 侧实现数组由MJS_TAG_ARRAY标签标识相关 API 集中在 mjs_array.c如mjs_mk_array()、mjs_array_push()、mjs_array_get()等其参数检查即会使用MJS_TYPE_OBJECT_ARRAY断言见 mjs_array.c。典型用法批量处理传感器数据、配置项列表等let leds [0, 1, 2, 3, 4]; for (let i 0; i leds.length; i) { // 处理每个元素 }五、ArrayBuffer 与 DataView二进制数据处理原文档最后两个类型ArrayBuffer、DataView是嵌入式脚本处理二进制数据的关键也是 mJS 相对标准 JS 的增强特性——它们直接对应 C 侧的原始内存缓冲。ArrayBuffer原始数据缓冲本质一段原始字节缓冲区raw data buffer本身不携带如何解读的语义你需要用 DataView 或类型化数组视图去读写它。C 侧实现引擎在struct mjs中维护一个专用缓冲array_buffers见 mjs_core.hmjs_mk_array_buf()负责分配并写入数据返回带MJS_TAG_ARRAY_BUF标签的值mjs_array_buf.cmjs_array_buf_get_ptr()则可拿到底层char*与字节长度mjs_array_buf.c。内置构造器全局对象提供ArrayBuffer构造器new ArrayBuffer(len)见 mjs_array_buf.c可用于一次性分配指定字节数的缓冲。slice 方法ArrayBuffer支持slice(start, end)截取子缓冲其实现mjs_array_buf_slice()位于 mjs_array_buf.c参数不合法非数字、越界、start end、参数个数不为 1 或 2时会返回undefined并置MJS_BAD_ARGS_ERROR。DataViewArrayBuffer 的读写接口本质提供访问 ArrayBuffer 内容的接口provides interface for accessing ArrayBuffer contents即以指定元素类型读写底层字节。C 侧实现DataView 对象内部保存两个字段buffer所引用的 ArrayBuffer与_t元素类型编号其标签为MJS_TAG_ARRAY_BUF_VIEW见 mjs_array_buf.c。读写操作最终落到 mjs_dataview.c 的mjs_mem_get_* / mjs_mem_set_*系列函数支持大端/小端两种字节序并提供有符号扩展。支持的元素类型mjs_array_buf_public.h 定义了 6 种视图类型对应元素字节宽度为Uint8Array无符号 8 位1 字节Int8Array有符号 8 位1 字节Uint16Array无符号 16 位2 字节Int16Array有符号 16 位2 字节Uint32Array无符号 32 位4 字节Int32Array有符号 32 位4 字节类型名即构造器名全部注册在全局对象上mjs_array_buf.c。三种构造方式对应 mjs_array_buf.c 的mjs_dataview_new()传入已有 ArrayBuffer——创建其视图传入数字——分配长度为len × 元素宽度的新缓冲并建立视图传入数组——用数组元素初始化新缓冲并建立视图。越界与对齐检查从视图读取越界索引返回undefinedmjs_array_buf.c写入越界返回MJS_TYPE_ERRORmjs_array_buf.c创建视图时若缓冲长度不是元素宽度的整数倍会直接报MJS_BAD_ARGS_ERRORmjs_array_buf.c。实战示例来自仓库的 array_buf_test.js仓库自带的示例 array_buf_test.js 完整演示了 ArrayBuffer 与 Uint8Array 的配合使用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]); }执行逻辑可逐行解读用数组[0..9]创建Uint8Array视图构造方式 3其底层 ArrayBuffer 长度为 10 字节输出len 10对底层buffer调用slice(2, 6)截取第 2~5 字节得到 4 字节的新 ArrayBuffer再包一层Uint8Array视图输出slice len 4遍历视图打印元素2 3 4 5。这个例子同时覆盖了文档中的Array数组字面量、ArrayBufferslice 与 byteLength与DataView/类型化数组Uint8Array 视图读写三类类型可作为上机验证的起点。六、类型判定与错误处理建议运行时判定脚本中可用typeof、 undefined、 null判断基础类型对象与数组可用Array.isArray()之类的惯用写法若引擎支持。在 C 侧引擎依据标签与enum mjs_type提供mjs_is_string()、mjs_is_number()、mjs_is_object()、mjs_is_array_buf()、mjs_is_data_view()等一整套判定函数。类型错误类型不匹配时引擎会产生MJS_TYPE_ERROR见 mjs_core_public.h 的错误枚举参数校验错误为MJS_BAD_ARGS_ERROR。例如向 ArrayBuffer 视图写入越界索引即触发MJS_TYPE_ERRORslice 参数非法则触发MJS_BAD_ARGS_ERROR。内存意识mJS 面向 RAM 受限的 MCU短字符串内联、NaN 装箱、ArrayBuffer 集中托管等设计都是为了节省内存。编写脚本时应优先使用短字符串数组保持元素类型一致对较大的二进制数据统一放入 ArrayBuffer 而不是拼成长字符串。七、小结一张图掌握 mJS 数据类型类型本质底层标签部分关键注意点string单字节字符序列MJS_TAG_STRING_I/_5/_O/_F不支持 UTF-8仅 ASCIInumberIEEE 754 double原生 NaN 布局整数/小数统一为 doublebooleantrue/falseMJS_TAG_BOOLEAN—foreignC 指针/函数指针MJS_TAG_FOREIGN不透明、仅可持有传递undefined未定义MJS_TAG_UNDEFINED属性缺失的返回值null显式空值MJS_TAG_NULL主动赋空用 nullObject命名字段容器MJS_TAG_OBJECT可经 FFI 映射 C 结构体Array索引集合元素类型一致MJS_TAG_ARRAY勿混入不同类型元素ArrayBuffer原始字节缓冲MJS_TAG_ARRAY_BUF需视图才能读写DataView缓冲读写接口MJS_TAG_ARRAY_BUF_VIEW6 种元素宽度1/2/4 字节在此基础上可进一步阅读仓库 documentation/js 下的 js_data_types 原文、js_about.md 了解 mJS 运行时全貌或直接查看 mjs 引擎源码 中对应类型的实现细节从而在 Flipper Zero 上写出更健壮的脚本应用。【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表