Auto.js 入门指南(十四)模块化与脚本复用

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!

第十四章:模块化与脚本复用


一、理论讲解:模块化开发的思想与优势

1.1 什么是模块化?

模块化是将一个复杂的程序,按照功能或职责,拆分成独立的、可重用的、相互协作的模块单元。每个模块只负责特定的功能,对外暴露接口,对内封装实现细节。

1.2 为什么需要模块化?

  • 代码复用:提高代码的可复用性,避免重复编写相似功能。
  • 维护性:降低代码的耦合度,使代码更容易理解、修改和维护。
  • 可读性:清晰的代码结构有助于团队协作和项目管理。
  • 避免命名冲突:模块内部的变量和函数不会污染全局作用域。
  • 按需加载:只加载需要的模块,减少内存占用和启动时间。
  • 团队协作:多人开发时,可以专注于各自模块,减少冲突。

1.3 Auto.js 中的模块化机制

Auto.js 支持 CommonJS 规范的 require 机制,允许脚本通过 module.exportsexports 导出功能,并通过 require() 导入使用。这使得我们可以像 Node.js 一样组织脚本,实现模块化开发。


二、代码示例:模块化与脚本复用全场景实战

2.1 基础模块导出与导入

javascript 复制代码
// modules/utils.js
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

module.exports = {
    add: add,
    subtract: subtract
};

// main.js
const utils = require('./modules/utils.js');
log("加法:" + utils.add(5, 3));
log("减法:" + utils.subtract(10, 4));

2.2 导出单个函数或变量

javascript 复制代码
// modules/greeting.js
module.exports = function(name) {
    return "你好," + name + "!";
};

// main.js
const greet = require('./modules/greeting.js');
log(greet("鲫小鱼"));

2.3 模块内部数据与对外接口

javascript 复制代码
// modules/dataStore.js
let _data = {}; // 私有数据

function set(key, value) {
    _data[key] = value;
}

function get(key) {
    return _data[key];
}

module.exports = {
    set: set,
    get: get
};

// main.js
const ds = require('./modules/dataStore.js');
ds.set("username", "AutojsUser");
log("用户名:" + ds.get("username"));
// log(_data); // 报错,_data是私有的

2.4 依赖其他模块的模块

javascript 复制代码
// modules/logger.js
function log(msg) {
    console.log(`[LOG] ${new Date().toLocaleTimeString()} ${msg}`);
}

module.exports = { log };

// modules/network.js
const logger = require('./logger.js');

function fetchData(url) {
    logger.log(`开始从 ${url} 获取数据...`);
    // 模拟网络请求
    sleep(1000);
    logger.log("数据获取完成。");
    return "some data";
}

module.exports = { fetchData };

// main.js
const network = require('./modules/network.js');
network.fetchData("https://api.example.com");

2.5 模块路径解析与常见问题

  • require('./path/to/module.js'):相对路径,常用。
  • require('/absolute/path/to/module.js'):绝对路径。
  • 建议使用相对路径,确保脚本在不同设备上的兼容性。
javascript 复制代码
// main.js 导入当前目录下的 module.js
const myModule = require('./myModule.js');

// main.js 导入上级目录下的 otherModule.js
const otherModule = require('../otherModule.js');

2.6 模块的循环依赖与解决方案

循环依赖可能导致模块加载顺序混乱或 undefined 错误。Auto.js 会返回一个未完全加载的模块对象,需要特别注意。

javascript 复制代码
// a.js
var b = require('./b.js');
log("a.js load");
exports.done = false;
log('in a, b.done = %j', b.done);
exports.done = true;

// b.js
var a = require('./a.js');
log("b.js load");
exports.done = false;
log('in b, a.done = %j', a.done);
exports.done = true;

// main.js
log('main starting');
var a = require('./a.js');
var b = require('./b.js');
log('in main, a.done=%j, b.done=%j', a.done, b.done);
/*
输出:
main starting
b.js load
in b, a.done = false
b.js load
a.js load
in a, b.done = true
in main, a.done=true, b.done=true
*/

解决方案:

  • 重新设计模块结构,避免循环依赖。
  • 延迟 require:在函数内部 require 模块,而不是在顶部。

2.7 动态加载模块与条件加载

javascript 复制代码
// 根据条件动态加载模块
let env = "prod"; // or "dev"
let configModule;
if (env === "dev") {
    configModule = require('./config_dev.js');
} else {
    configModule = require('./config_prod.js');
}
log("加载的配置:" + JSON.stringify(configModule.settings));

// config_dev.js
module.exports = { settings: { debug: true, api: "dev.api.com" } };

// config_prod.js
module.exports = { settings: { debug: false, api: "prod.api.com" } };

2.8 模块化组件与 UI 复用

javascript 复制代码
// components/MyButton.js
function createButton(text, onClick) {
    return <button text={text} onClick={onClick} />;
}

module.exports = { createButton };

// main.js
"ui";
const MyButton = require('./components/MyButton.js');

ui.layout(
    <vertical padding="16">
        {MyButton.createButton("点击我", () => {
            toast("按钮被点击了");
        })}
        {MyButton.createButton("另一个按钮", () => {
            log("另一个按钮被点击了");
        })}
    </vertical>
);

2.9 发布与共享模块

  • 将通用模块打包成 .js 文件或文件夹。
  • 可以在 Auto.js 社区、GitHub 等平台分享。
  • 建议附带 README.md 说明模块功能、使用方法、依赖等。

2.10 ES6 模块的未来展望 (虽然 Auto.js 暂不支持)

虽然 Auto.js 当前主要支持 CommonJS,但了解 ES6 模块(import/export)有助于未来发展,它提供了更强大的静态分析和 Tree Shaking 能力。


三、实战项目:通用弹窗处理模块

3.1 项目需求

开发一个通用的弹窗处理模块,能够自动识别并关闭常见的系统弹窗、广告弹窗、升级提示等。该模块应具备高可复用性,可被其他脚本轻松引入。

3.2 项目结构

plaintext 复制代码
autojs-modules-demo/
├── main.js
├── modules/
│   ├── popupHandler.js
│   └── logger.js
├── logs/
│   └── app.log
└── README.md

3.3 logger.js 日志模块 (复用)

javascript 复制代码
// modules/logger.js
function log(msg, level = "INFO") {
    let line = `[${new Date().toLocaleTimeString()}][${level}] ${msg}`;
    files.append("../logs/app.log", line + "\n");
    console.log(line); // 同时输出到控制台
}

module.exports = { log };

3.4 popupHandler.js 弹窗处理模块

javascript 复制代码
// modules/popupHandler.js
const logger = require('./logger.js');

const COMMON_POPUP_KEYWORDS = [
    "关闭", "取消", "我知道了", "以后再说", "跳过", "不再提示", "确定",
    "允许", "拒绝", "同意", "拒绝并退出", "稍后", "稍后再说"
];

let watcherThread = null;

/**
 * 启动弹窗监控线程
 * @param {Array<string>} customKeywords 自定义弹窗关键词
 * @param {number} interval 检测间隔(毫秒)
 */
function startPopupWatcher(customKeywords = [], interval = 1000) {
    if (watcherThread && watcherThread.isAlive()) {
        logger.log("弹窗监控已在运行,无需重复启动。", "WARN");
        return;
    }

    const allKeywords = COMMON_POPUP_KEYWORDS.concat(customKeywords);

    watcherThread = threads.start(function() {
        logger.log("弹窗监控线程已启动。");
        while (true) {
            let handled = false;
            for (let i = 0; i < allKeywords.length; i++) {
                let keyword = allKeywords[i];
                let uiObject = text(keyword).findOne(200) || desc(keyword).findOne(200);
                if (uiObject) {
                    try {
                        uiObject.click();
                        logger.log(`自动关闭弹窗:'${keyword}'`, "INFO");
                        handled = true;
                        break; // 找到并点击一个就退出当前循环,等待下次检测
                    } catch (e) {
                        logger.log(`点击弹窗失败 '${keyword}': ${e}`, "ERROR");
                    }
                }
            }
            if (handled) {
                sleep(interval + 500); // 弹窗处理后稍微多等一下,避免误触
            } else {
                sleep(interval);
            }
            // 检测线程是否被中断
            if (threads.currentThread().isInterrupted()) {
                logger.log("弹窗监控线程已中断。", "INFO");
                break;
            }
        }
    });
    logger.log("弹窗监控线程已成功启动。", "INFO");
}

/**
 * 停止弹窗监控线程
 */
function stopPopupWatcher() {
    if (watcherThread && watcherThread.isAlive()) {
        watcherThread.interrupt(); // 中断线程
        watcherThread = null;
        logger.log("弹窗监控线程已停止。", "INFO");
    } else {
        logger.log("弹窗监控未运行。", "WARN");
    }
}

module.exports = {
    startPopupWatcher,
    stopPopupWatcher
};

3.5 main.js 主入口脚本

javascript 复制代码
// main.js
const popupHandler = require('./modules/popupHandler.js');
const logger = require('./modules/logger.js');

// 启动弹窗监控,可以传入自定义关键词
popupHandler.startPopupWatcher(["立即升级", "领取奖励"], 800);

logger.log("主脚本开始执行...");

// 模拟主流程操作
for (let i = 0; i < 5; i++) {
    logger.log(`主流程执行第 ${i + 1} 步...`);
    sleep(2000);
    // 模拟出现一个弹窗
    if (i === 2) {
        // 为了演示,这里我们手动模拟一个UI,实际中是App界面弹窗
        threads.start(function(){
            "ui";
            ui.layout(<frame><button id="closeBtn" text="关闭" w="100dp" h="50dp"/></frame>);
            ui.closeBtn.on("click",()=>{ ui.finish(); });
            setTimeout(() => ui.finish(), 3000); // 3秒后自动消失
        });
        logger.log("模拟弹窗出现。");
    }
}

logger.log("主脚本执行完毕。");

// 停止弹窗监控(可选,如果主脚本结束后不再需要)
// popupHandler.stopPopupWatcher();

3.6 运行效果与分析

  • 运行 main.js 后,弹窗监控线程会在后台持续运行。
  • 当模拟的"关闭"弹窗出现时,弹窗处理模块会自动识别并点击关闭按钮。
  • 日志模块会记录弹窗处理的历史,方便调试。
  • 该模块可以在其他 Auto.js 脚本中被 require 引入,实现弹窗处理的复用。

四、常见问题与解决方案

问题 解决方案
require 路径错误/模块找不到 检查模块文件路径是否正确,注意相对路径和绝对路径的使用
模块变量污染/命名冲突 确保模块导出明确,避免全局变量;使用 let/const 声明局部变量
循环依赖导致 undefined 重新设计模块结构,避免循环依赖;或使用延迟 require
模块加载性能问题 避免加载不必要的模块;优化模块内部逻辑;按需加载
模块更新后不生效 Auto.js 默认有模块缓存,重启脚本或清除缓存尝试
模块化后调试困难 利用日志模块详细记录模块间的调用和数据流
线程安全问题(多线程模块) 对共享资源加锁或使用同步机制,避免数据竞争
模块功能单一,复用性差 提取通用逻辑,使模块功能更抽象、更灵活
UI 模块无法直接操作 UI UI 操作必须在主线程执行,子线程通过 ui.run() 调度

五、性能优化建议

  • 按需加载 :只在需要时 require 模块,尤其对于大型或不常用的模块。
  • 精简模块:模块功能应尽可能单一,避免大而全的"巨石模块"。
  • 避免不必要的 require :如果在循环或高频函数内部 require 模块,可能造成性能开销。
  • 缓存模块引用 :一旦 require 过的模块会被缓存,不要重复 require,直接使用已有的引用。
  • 优化模块内部逻辑:确保模块内部的代码高效,减少不必要的计算和 I/O。
  • 使用 console.log 进行调试 :避免在生产环境大量使用 toast,它会占用 UI 线程资源。
  • 资源释放:模块中如果创建了持久性资源(如线程、定时器),确保在不再需要时及时释放。
  • 错误处理:模块内部的错误应妥善处理并记录日志,避免因一个模块的崩溃导致整个脚本停止。
  • 打包与压缩:对于大型项目,可以考虑将多个模块打包成一个文件,或对模块进行压缩混淆,减少文件大小和加载时间(Auto.js Pro 支持)。

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

相关推荐
wx_lidysun3 小时前
Nextjs学习笔记
前端·react·next
无羡仙6 小时前
从零构建 Vue 弹窗组件
前端·vue.js
xiaolizi5674897 小时前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
阿杰100017 小时前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
梨落秋霜7 小时前
Python入门篇【文件处理】
android·java·python
源心锁7 小时前
👋 手搓 gzip 实现的文件分块压缩上传
前端·javascript
源心锁7 小时前
丧心病狂!在浏览器全天候记录用户行为排障
前端·架构
GIS之路7 小时前
GDAL 实现投影转换
前端
烛阴8 小时前
从“无”到“有”:手动实现一个 3D 渲染循环全过程
前端·webgl·three.js
BD_Marathon8 小时前
SpringBoot——辅助功能之切换web服务器
服务器·前端·spring boot