QuickJS 开发指南

1. 简介

QuickJS 是一款轻量级可嵌入的 JavaScript 引擎,支持 ES2020 规范(包括模块、异步生成器等),并扩展了大整数、大浮点数等数学功能。

Sifli 基于官方版本适配了 RT-Thread 和 LVGL,支持通过 JavaScript 开发表盘、应用及 AOD(息屏显示),沿用 C 应用框架的调度逻辑。

2. 核心框架说明

2.1 应用框架支持

Sifli 具备明确的应用框架,提供应用 / 表盘 / AOD 的注册接口。系统启动时自动扫描 qjs 目录,按类型识别子目录:

  • JA_xxx: JS应用目录,主程序是目录中JA_xxx_main.js, 需定义与目录名一致的类并注册到全局变量

  • JW_xxx: JS 表盘目录,主程序为 JW_xxx_main.js,需定义与目录名一致的类并注册到全局变量

  • AOD_xxx: JS 息屏应用目录,主程序为 AOD_xxx_main.js,需定义与目录名一致的类并注册到全局变量

所有应用按类型注册到对应 C 框架,由 C 框架统一调度执行。

2.2 LVGL的支持

LVGL 支持通过三种形式提供给 JS 调用:

  • obj class 封装lv_obj_xxx函数,输出lvobj类,每个方法对应相应的C函数

  • obj extension 封装lv_class_xxx函数,与生成的class.js 配合输出具体控件类(如label.js对应`label类),使用时通过 import label from “/support_script/qjs/label.js” 导入

  • functions 封装LVGL中不属于上述两类的函数,归属于lv模块,调用方式如 lv.gui_app_run(app) 注意

  • 入参和返回值类型非int/bool/color16_t/string/object的函数无法自动生成

  • lv_obj_add_event_cb 因涉及函数指针,通过lvobj 模块的 set_event_cb 方法实现事件传递

3.快速上手

quickjs支持板级/模拟器运行,步骤如下:

  1. 打开HCPU工程/SIMU工程quickjs依赖宏

  2. 勾选预置qjs脚本和应用

  3. 勾选编译选项JS应用,开始编译

demo源码位于\solution\examples_dynamic_app\qjs目录,各应用功能如下:

应用

说明

JA_app1

条形码,二维码,文件IO操作,task使用,多页面功能

JA_app2

按键,旋钮功能

JA_app3

数据存储和读取功能

JA_app4

图表功能,可以修改chart_type显示不同风格

JA_app5

多实例动画

JW_wf1

模拟表盘功能

JW_wf2

序列帧功能

JW_wf4

GIF功能

JW_wf5

ezipa功能 (需硬件支持,不支持模拟器)

JW_wf6

数据订阅功能(模拟器无数据)

AOD_wf1

熄屏显示功能

提示

  • 仅修改JS应用代码时,单独编译烧录JS应用即可

4. 目录结构

4.1 框架代码

QJS 框架代码位于qjs_framework目录,包含应用框架、页面调度、LVGL 模块等

4.1.1 生成代码

QJS c映射代码位于qjs_generated目录:

  • lv_qjs_ext_obj.c obj extension封装集合

  • lv_qjs_function.c functions封装集合

  • lv_qjs_obj.c obj class封装集合

  • lv_qjs_generated.c 文件包含和一些编译打桩函数

4.1.2 类代码

js 类文件是obj externsion的封装,代码位于如下路径,需通过butterfli预置到文件系统使用。

4.1.3 应用代码

应用代码目录结构如图

  • qjs_aod 存放AOD应用,子目录以AOD_开头

  • qjs_app 存放普通应用,子目录以JA_开头

  • qjs_wf 存放表盘应用,子目录以JW_开头

  • 辅助文件:

    • readme.ini 编译依赖配置,说明参考 外置应用编译依赖

    • JA_xxx_language.js,多语言文件(由Excel转换生成)

    • 主程序文件:JA_xxx_main.js(命名固定)

    • 子页面文件:JA_xxx_page1.js(需与代码中页面名匹配)

    • resource目录,资源放置目录,说明参考资源位置

    • 应用图标: 必须命名为thumb.png

5. 接口生成

QJS 接口生成支持自动生成和手动添加两种方式。建议优先使用自动生成,减少内存泄露风险。新增接口建议统一放在lv_qjs_generated.c/h 中。

5.1 自动生成

适用于简单接口,需满足:

  • 参数和返回值类型仅限int,bool,lv_color_t,string,lv_obj_t*

  • 接口名以lv_开头

  • 入参个数最大支持8

  • 避免编译非LVGL相关文件

5.1.1 特殊处理

  • 继承规则:obj_extension接口父类非lv_obj,需在inherit_list.py中指定父类。

  • 字符串参数: C 接口参数类型为 void* 但实际为字符串时,在 string_list.py 中声明

5.1.2 相关脚本

脚本

目录

说明

sol_gen.sh

sdk\external\micropython\lib\lv_bindings

linux系统使用,生成json中间件

qjs_gen_blacklist.bat

solution\components\quickjs\script

windows系统使用,生成black_list.py

qjs_gen_js_class.bat

solution\components\quickjs\script

windows系统使用,生成ext obj对应的.js

qjs_gen_lv_ext_obj.bat

solution\components\quickjs\script

windows系统使用,生成对ext obj的C支持

qjs_gen_lv_functions.bat

solution\components\quickjs\script

windows系统使用,生成对function的C支持

qjs_gen_lv_obj.bat

solution\components\quickjs\script

windows系统使用,生成对lv obj的C支持

inherit_list.py

solution\components\quickjs\script

指明生成JS class继承的模块

string_list.py

solution\components\quickjs\script

指定接口参数为字符串

5.1.3 执行步骤

  1. 将新增接口实现放入lv_qjs_generated.c,声明放入 lv_qjs_generated.h(ext obj 类型直接在头文件中 include)

  2. Linux系统:

    • 安装 Python 2.7(建议 Ubuntu 18.04.6 LTS)

    • 进入 /sdk/external/micropython/lib/lv_bindings 目录

    • 执行带参脚本,如 ./sol_gen.sh nand 16 qjs 生成中间件

  3. Windows系统:

    • 进入 solution/components/quickjs/script 目录

    • 执行 qjs_gen_blacklist.bat 生成 blacklist.py(黑名单中的函数不生成)

    • 根据接口类型执行相应脚本更新代码:

      • lv obj类型: qjs_gen_lv_obj.bat(更新 lv_qjs_obj.c

      • function 类型: qjs_gen_lv_functions.bat(更新 lv_qjs_functions.c

      • obj extension 类型 qjs_gen_lv_ext_obj.bat(更新 lv_qjs_ext_obj.c)+ qjs_gen_js_class.bat(更新 JS 类文件) 注意

  • 脚本执行报错可能因文件格式(非 Unix 格式)或编码问题导致

  • 黑名单中的函数不会生成

  • 不支持的接口会在生成的 .c 文件中提示,可搜索查看原因

  • JS类文件位于solution/components/support_script/qjs目录

5.2 手动添加

适用于对 QuickJS 有一定了解的开发者,可直接修改对应 C 文件添加接口。

5.2.1 类型对应表

类型

文件

枚举定义表

函数格式表

函数列表

回调接口

lvobj

lv_qjs_obj.c

JS_lv_obj_methods_enum

lv_obj_protos

js_lv_obj_methods

lv_obj_call_method

ext obj

lv_qjs_ext_obj.c

JS_lv_ext_funcs_enum

lv_ext_funcs_proto

js_lv_ext_funcs

lv_ext_call_func

functions

lv_qjs_functions.c

JS_lv_funcs_enum

lv_funcs_proto

js_lv_funcs

lv_obj_call_method

5.2.2 实现步骤

  1. 定义枚举值:

    • functions 和 obj extension 类型:LVFUNC + 去掉lv_前缀的函数名(如lv_version_major->LVFUNC_version_major

    • lvobj类型:LVOBJ + 去掉lv_obj_前缀的函数名(如lv_obj_clean->LVOBJ_clean

  2. 添加数据格式:
    在函数格式表中按**枚举顺序**添加,包含 name(函数名)、param_type(参数类型数组,根据函数如参顺序依次填入类型)、return_type(返回值类型)

  3. 添加函数列表:
    **枚举顺序**添加 JS_CFUNC_MAGIC_DEF 定义,参数需与格式表匹配(param1 函数名, param2 函数参数个数,和步骤2中param_type的个数匹配,param3 函数回调接口, param4 是步骤1中的枚举值)

  4. 实现回调函数:

  • 入参通过 param[0]param[1] 等获取(lvobj 类型第一个参数为 s->lv_obj

  • 返回值处理:

    • int/lv_obj_t*JS_NewInt32

    • bool : JS_NewBool

    • 字符串: JS_NewString

    • lv_color_t : 先lv_color_to32 转换,再 JS_NewInt32

5.2.3 类型定义

enum {
    LVTYPE_none=0, // 无类型
    LVTYPE_int, // 整数
    LVTYPE_bool, // 布尔值
    LVTYPE_func,   // 函数指针
    LVTYPE_object, // 对象
    LVTYPE_color, // 颜色
    LVTYPE_string, // 字符串
};

5.2.4 示例代码

//枚举定义
typedef enum
{
    ...,
    LVFUNC_version_info,
    LVFUNC_version_major,
    LVFUNC_version_minor,
    LVFUNC_version_patch,
}JS_lv_funcs_enum;

//函数格式表
const JS_lv_protos lv_funcs_proto[]=
{
    ....,
    //顺序要和枚举中的顺序保持对应
    {
        .name = "version_info",
        .return_type = LVTYPE_string
    },
    {
        .name = "version_major",
        .return_type = LVTYPE_int
    },
    {
        .name = "version_minor",
        .return_type = LVTYPE_int
    },
    {
        .name = "version_patch",
        .return_type = LVTYPE_int
    }, 
};

//函数列表
const JSCFunctionListEntry js_lv_funcs[] =
{
    ...,
    //顺序要和枚举中的顺序保持对应
    JS_CFUNC_MAGIC_DEF("version_info", 0, js_lv_func, LVFUNC_version_info),
    JS_CFUNC_MAGIC_DEF("version_major", 0, js_lv_func, LVFUNC_version_major),
    JS_CFUNC_MAGIC_DEF("version_minor", 0, js_lv_func, LVFUNC_version_minor),
    JS_CFUNC_MAGIC_DEF("version_patch", 0, js_lv_func, LVFUNC_version_patch),    
};

//回调实现
static JSValue  lv_call_func(JSContext *ctx, int magic, JSValueConst param[]) = 
{
    JSValue  r = JS_UNDEFINED;
    switch(magic)
    {
    ...,
    case LVFUNC_version_info:
    {
        char *r_in = (char *)lv_version_info();
        r = JS_NewString(ctx, r_in);
        break;
    }
    case LVFUNC_version_major:
    {
        int r_in = lv_version_major();
        r = JS_NewInt32(ctx, r_in);
        break;
    }
    case LVFUNC_version_minor:
    {
        int r_in = lv_version_minor();
        r = JS_NewInt32(ctx, r_in);
        break;
    }
    case LVFUNC_version_patch:
    {
        int r_in = lv_version_patch();
        r = JS_NewInt32(ctx, r_in);
        break;
    }
    }
    return r;
}

lv obj类型和ext obj类型添加方式类似。

5.2.5 JS文件添加(obj extension类型)

在 support_script/qjs/ 目录添加对应 JS 类文件,示例(lvsfbarcode.js)

import * as lv from "lv"; //lv模块
import * as lvext from "lvext"; //lvext模块
import {img} from "/support_script/qjs/img.js"; //img模块
export class lvsfbarcode extends img { //继承自img,需要和C代码控件class变量中的base_class保持一致
    constructor(parent,parentobj=undefined) {
        var nativeobj=parentobj;
        if(nativeobj==undefined) {
            nativeobj=lvext.lvsfbarcode_create(parent);
        }
        super(parent,nativeobj);
        this.nativeobj=nativeobj;
        this.set_obj(this.nativeobj);
    }
    set_text(text){
        return lvext.lvsfbarcode_set_text(this.nativeobj, text);
    }
    set_line_color(index){
        return lvext.lvsfbarcode_set_line_color(this.nativeobj, index);
    }
    set_parse(parse){
        return lvext.lvsfbarcode_set_parse(this.nativeobj, parse);
    }
    set_dir(dir){
        return lvext.lvsfbarcode_set_dir(this.nativeobj, dir);
    }
    //其他方法...
}

注意

  • 建议通过统一接口封装同类功能(如通过枚举值实现通用 get/set 方法),减少生成依赖和 ROM 占用。

6. 功能支持

6.1 数据订阅

JS 控件可通过 lvobj 模块的 bind 方法订阅数据,数据更新时由 C 端通知:

    label = new label(this.root);


    // 回调函数:id(字符串)、type(整数)、val(可选数据)
    function callback(id, type, val){
            //do something
    }

    //id 字符串类型,id为NULL时,type必须为唯一值
    //type 整形
    //回调函数
    label.bind(id, type, callback);

建议

  • 每个控件建议只订阅一种类型数据,简化回调处理

  • 可封装 get 接口在回调中直接获取数据,无需关心 val 类型

  • 控件取消订阅由框架自动管理,无需手动处理

  • 示例参考JA_app1

6.2 编码器(旋钮)

lvapp 模块的 wheel() 方法用于注册 / 反注册旋钮回调,通常在 resume() 中注册,pause() 中反注册:

    wheel_handler(key) {
        // key:17(上),18(下),见lv_enums.js   
    }

    resume() {
        this.wheel( //传入参数时表示注册
            function (key) {
                this.wheel_handler(key);
            }
        );
    } 
    pause(){
        this.wheel(); //没有参数表示反注册
    }

6.3 按键

lvapp 模块的 keypad() 方法用于注册 / 反注册按键回调:

    keypad_handler(key, state) {
        //key 按键,state:1(按下),0(松手)
        if(key == lv_enums.KEY_HOME && state == 1){
            return 1;   //返回1不执行按键默认功能
        }
        return 0; //返回0执行按键默认功能
    }

    resume() {
        this.keypad( //传入参数时表示注册
            function (key, state) {
                return this.keypad_handler(key, state);
            }
        );
    } 
    pause(){
        this.keypad(); //没有参数表示反注册
    }

注意

  • 按键回调需返回值,框架根据返回值决定是否执行默认功能

  • 示例参考JA_app2

6.4 数据存储

nvm 模块通过文件系统实现数据存储(序列化 / 反序列化为 JSON):

    import * as nvm from "nvm" //头文件引入

    //示例数据
    var user = {
        id:1,
        name:"Bob",
        scores:[90,85]
    };

    //存储
    var str_data = JSON.stringify(user)
    nvm.write('user.json', str_data);

    // 读取
    var read_string = nvm.read('user.json');
    var conv_obj = JSON.parse(read_string);

注意: 需处理异常场景(文件不存在、格式错误等)

使用示例参考JA_app3

6.5 多语言

通过 Excel 表转换生成的 JS 文件实现多语言支持:

  1. 导入接口: import {app_get_str} from "./JA_xxx_language.js", 如import {app_get_str} from "./JA_app1_language.js"

  2. 获取字符串: app_get_str(“key”,“default_string”); 注意 应用标题需要显示调用以避免被工具移除

    globalThis.JA_app1 = JA_app1;
    app_get_str("key_qjs_name","key_test"); // 保留应用名多语言条目

6.6 文件操作

os 模块提供文件操作接口:

    /*Open a file. Return a handle or < 0 if error.
    Supported flags:
    O_RDONLY
    O_WRONLY
    O_RDWR
    O_APPEND
    O_CREAT
    O_EXCL
    O_TRUNC
    */
    open(filename, flags, mode = 0o666)

    //Seek in the file. Use std.SEEK_* for whence. offset is either a number or a BigInt. 
    //If offset is a BigInt, a BigInt is returned too
    seek(fd, offset, whence)

    //Read length bytes from the file handle fd to the ArrayBuffer buffer at byte position offset. 
    //Return the number of read bytes or < 0 if error
    read(fd, buffer, offset, length)
    
    //Write length bytes to the file handle fd from the ArrayBuffer buffer at byte position offset. 
    //Return the number of written bytes or < 0 if error.    
    write(fd, buffer, offset, length)

    //Return [str, err] where str is the current working directory and err the error code.
    getcwd()

    //Change the current directory. Return 0 if OK or -errno.
    chdir(path)

    //Create a directory at path. Return 0 if OK or -errno.
    mkdir(path, mode = 0o777)

    //Return [array, err] where array is an array of strings containing the filenames of the directory path. 
    //err is the error code. array contains at least "." and ".." if successful.
    readdir(path)

    //Close the file handle fd 
    close(fd)

注意

6.7 动画

anim 模块支持多实例动画,支持接口参考solution\components\quickjs\qjs_framework\lvgl_anim_qjs.c 示例:

import * as lv from "lv"
import { app } from "lvapp"
import { anim }  from "anim"    //anim 模块

class ja_app extends app {
    constructor() {
        super();
        this.anim = undefined;
    }

    start() {
        print("start");
        this.anim = new anim();
        this.anim.set_values(0,  200); // 动画范围
        this.anim.set_path(this.anim.PATH_LINEAR) // 动画曲线
        this.anim.set_time(1000);  // 动画时长(ms)
        this.anim.set_delay(2000);  // 动画延时(ms)
        this.anim.set_start_cb(function(anim){  // 开始回调
            print("anim start");
        });
        this.anim.set_ready_cb(function(anim){ //结束回调
            print("anim ready");
        });
        this.anim.set_exec_cb(function(val){ // 执行回调
            print(val);
        });
        this.anim.start();
    }

    resume() {
        print("resume");
    }
    pause(){
        print("pause");
    }
    stop(){
        print("stop");
        this.anim.del(); // 销毁动画,避免循环引用
    }
}

globalThis.JA_app5 = {
    root:ja_app
};
app_get_str("key_qjs_name","ja_app5");

注意 动画实例数量会影响页面帧率,退出时需销毁避免内存泄露
anim使用demo参考JA_app5

6.8 SENSOR

全局 sensor 模块提供传感器数据获取(仅支持 getter),支持接口参考solution\components\quickjs\qjs_framework\lvgl_sensor_qjs.c

    var level = sensor.BatLevel; // 获取电池电量

7. 应用开发

7.1 基础规范

需创建继承自 lvapp 的类,并实现核心方法使应用可被框架调度。

7.1.1 父类方法

父类 class app 提供了以下可使用的方法,子类可根据需求进行实现或调用:

方法名

功能描述

start()

应用初始化(必实现),创建页面、初始化数据

resume()

应用恢复(必实现),一般用以开启任务

pause()

应用暂停(必实现),一般用以暂停任务

stop()

应用销毁(必实现),一般用以释放自定义控件数据

root()

获取父控件,新控件需基于此创建

path()

获取应用路径,用于拼接资源路径

keypad()

注册 / 反注册按键回调

wheel()

注册 / 反注册编码器回调

task()

创建 / 销毁任务(一般在 resume/pause 中调用)

7.1.2 任务(Task)示例

pause(){
    this.task(); // 销毁
}

resume(){
    this.task(){
        function(){
            //do something; //处理
        },
        1000    //刷新间隔
    };
}

注意

  • 禁止空方法(无逻辑时需添加占位代码如 print)。

  • 禁止使用 LVGL 对象的 user_data(框架占用)

  • 任务避免频繁刷新和复杂计算,建议将处理逻辑交给 C 层

7.2 注册接口

应用需通过全局唯一对象注册接口:

  1. 主页面(如JA_app1_main.js):

    // 注册主页面,变量名需与目录名一致
    globalThis.JA_app1 = { 
        root : app_root,
    };
  1. 子页面(如JA_app1_page1.js)

    class page1 extends app { ... }

    // 追加子页面,属性名需与文件名后半部一致
    globalThis.JA_app1.page1 = app_page1;

8. 开发示例

8.1 应用(APP)示例

  1. 主页面xxx_main.js(如JA_app1_main.js):

import * as lv from "lv"    //lv obj module
import * as os from "os"    //os模块
import * as std from "std"  //std模块
import {app} from "lvapp"   //app模块
import * as lv_enums from "/support_script/qjs/lv_enums.js" //枚举模块
import {qrcode} from  "/support_script/qjs/qrcode.js"      //qrcode控件
import {lvsfbarcode} from "/support_script/qjs/lvsfbarcode.js" //barcode 控件
import {app_get_str} from "./JA_app1_language.js"          //引入多语言接口


//arraybuffer转字符串
function arrayBufferToString(buffer){
    return String.fromCharCode.apply(null, new Uint16Array(buffer));
}

//字符串转arraybuffer
function stringToArrayBuffer(str) {
    var buf = new ArrayBuffer(str.length * 2);
    var bufView = new Uint16Array(buf);
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

var string = "0705632441974";
var txt = "0123456-ABC-abcd";

class JA_app1 extends app{    //继承父类app, class 名需要和目录名相同
    constructor() {
        super();    //构造函数中调用父类app的构造函数
        this.count = 0; //初始化变量
    }

    refresh() {
        this.count++;
        print("count ", this.count);
    }

    start() {

        // Demo for QRcode        
        this.qrcode = new qrcode(this.root()); //this.root() 获取当前parent
        this.qrcode.setparam(200, lv.color_make(0xc0,0xc0,0xc0), lv.color_make(0xFF,0xFF,0xFF));
        this.qrcode.set_text(txt, txt.length); 
        this.qrcode.align(lv_enums.ALIGN_TOP_MID, 0, 0);
        
        this.qrcode.set_event_cb( //set_event_cb是固定控件回调处理
            function(event){
                if (event==lv_enums.EVENT_SHORT_CLICKED){
                    print("qrcode event ", event);
                    lv.gui_app_self_exit(); //退出应用
                }
            }
        );
                        
        // Demo for Barcode
        this.barcode = new lvsfbarcode(this.root());
        this.barcode.set_size(300, 100);
        this.barcode.set_text(string, 66);
        this.barcode.align(lv_enums.ALIGN_BOTTOM_MID, 0, 0);
        this.barcode.set_event_cb(
            function(event){
                if(event == lv_enums.EVENT_SHORT_CLICKED){
                    print("barcode event ", event);
                    lv.qjs_app_page_create("page1"); //创建子页面page1
                }
            }
        );  
        
        print("Started\n");
    }    
    resume() {

        // Demo for OS file access
        this.f=os.open("/a.txt", os.O_RDWR | os.O_CREAT | os.O_TRUNC); //打开文件
        print("f=",this.f);
        if(this.f > 0){
            const buffer1 = stringToArrayBuffer('Hello, QuickJS File Operations!');
            os.write(this.f, buffer1, 0, buffer1.byteLength) //写入数据
            print("write:", arrayBufferToString(buffer1));
            os.seek(this.f, std.SEEK_SET, 0);
            const buffer = new ArrayBuffer(buffer1.byteLength);
            os.read(this.f,buffer,0, buffer1.byteLength); //读取数据
            print("read:", arrayBufferToString(buffer));
            os.close(this.f);  //关闭文件
        }
        
        // Demo for OS directory
        os.chdir("/support_script/qjs"); //切换目录
        print(os.getcwd());             //获取当前路径
        var local_files=os.readdir("/"); //读取目录
        print(local_files[0].length);
        for (var i=0;i<local_files[0].length;i++)
        {         
            print(local_files[0][i]);
        }

        // Demo for task
        this.task(
            function() {
                this.refresh(); //刷新实现
            }
            , 1000  //刷新间隔
        ); 
    }
    pause(){
        this.task(); //销毁task
    }
    stop(){
        print("stop"); //stop没有实际执行内容,增加打印。
    }    
}
//初始化赋值
globalThis.JA_app1 = {
    root : JA_app1,
};
app_get_str("key_qjs_name","key_test"); //应用标题申明
  1. 子页面 应用名_页面名.js(如JA_app1_page1.js

import * as lv from "lv"
import * as os from "os"
import * as lv_enums from "/support_script/qjs/lv_enums.js"
import {app} from "lvapp"
import {label} from "/support_script/qjs/label.js"

class page1 extends app{
    constructor() {
        super();
    }
    start(){
        this.label = new label(this.root());
        this.label.set_local_font(lv_enums.FONT_SUPER, lv.color_make(0xff,0xff,0xff));
        this.label.set_text("subpage 1");
        this.label.align(lv_enums.ALIGN_CENTER, 0, 0);
        
        this.label1 = new label(this.root());
        this.label1.set_local_font(lv_enums.FONT_TITLE, lv.color_make(0xff,0xff,0xff));
        this.label1.set_text("click to subpage 2");
        this.label1.align(lv_enums.ALIGN_CENTER, 0, 60);
        this.label1.add_flag(lv_enums.FLAG_CLICKABLE);
        this.label1.set_event_cb(
            function(event){
                if(event == lv_enums.EVENT_SHORT_CLICKED)
                    lv.qjs_app_page_create("page2");
            }
        );  
        
    }
    resume(){
        print("subpage resume");
    }
    pause(){
        print("subpage pause");
    }
    stop(){
        print("subpage stop");
    }
}

globalThis.JA_app1.page1 = page1;

8.2 表盘(WF)示例

import * as lv from "lv"
import {app} from "lvapp"
import {analogclk} from "/support_script/qjs/analogclk.js"

class JW_wf1 extends app{ //继承父类app, class 名需要和目录名相同
    constructor() {
        super();
    }    
    start() {
        this.anaclk=new analogclk(this.root());
        this.anaclk.pos_off(10,10,105);
        this.anaclk.img(this.path() + "bg.bin", this.path() + "hour.bin", this.path() + 
                        "minute.bin", this.path() + "second.bin"); //this.path()获取当前路径
        this.rate = 30;
    }    
    pause() {
        this.anaclk.refr_inteval(0);//暂停模拟表盘
    }    
    resume() {
        this.anaclk.refr_inteval(30);//刷新模拟表盘
    }    
}
globalThis.JW_wf1 = {
    root : JW_wf1,
};

8.3 AOD示例

import * as lv from "lv"
import {app} from "lvapp"
import * as lv_enums from "/support_script/qjs/lv_enums.js"
import {analogclk} from "/support_script/qjs/analogclk.js"

class AOD_wf1 extends app{
    constructor() {
        super();
    }    
    start() {
        this.anaclk=new analogclk(this.root());
        this.anaclk.pos_off(10,10,105);
        this.anaclk.img(this.path() + "bg.bin", this.path() + "hour.bin", this.path() + "minute.bin", this.path() + "second.bin");
        this.rate = 30;
    }    
    pause() {
        this.anaclk.refr_inteval(0);
    }    
    resume() {
        this.anaclk.refr_inteval(this.rate);
    }    
}
globalThis.AOD_wf1 = {
    root : AOD_wf1,
};

9. 开发建议

  • 枚举类型 JS 文件按类型拆分,避免过大影响解释效率

  • 变量名精简,减少内存占用

  • 复杂计算逻辑建议在 C 层实现,JS 仅负责调用接口