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 支持)。

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

相关推荐
再吃一根胡萝卜几秒前
简单了解react-monaco-editor
前端
独立开阀者_FwtCoder1 分钟前
Nginx 部署负载均衡服务全解析
前端·javascript·后端
whysqwhw4 分钟前
Egloo 架构设计
android
whysqwhw10 分钟前
Egloo 快速入门指南
android
哒哒哒52852029 分钟前
HTTP缓存
前端·面试
T___31 分钟前
从入门到放弃?带你重新认识 Headless UI
前端·设计模式
wordbaby32 分钟前
React Router 中调用 Actions 的三种方式详解
前端·react.js
黄丽萍38 分钟前
前端Vue3项目代码开发规范
前端
curdcv_po42 分钟前
🏄公司报销,培养我成一名 WebGL 工程师⛵️
前端
Jolyne_1 小时前
前端常用的树处理方法总结
前端·算法·面试