|
| 1 | +这是一份关于 Lua 模块化(Module)组织与编写的速查表(Cheatsheet)。Lua 的模块机制非常灵活,本质上是“利用 Table 和 Closure(闭包)来管理作用域”。 |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +# Lua 模块化组织 Cheatsheet |
| 6 | + |
| 7 | +## 1. 标准模板 (The Golden Standard) |
| 8 | + |
| 9 | +这是目前 Lua (5.1+) 最推荐的模块写法。**不要使用** 已废弃的 `module()` 函数。 |
| 10 | + |
| 11 | +```lua |
| 12 | +-- 文件名: mymodule.lua |
| 13 | +local M = {} -- 1. 定义模块表 |
| 14 | + |
| 15 | +-- 2. 定义私有变量/函数 (Local) |
| 16 | +local default_scale = 1.5 |
| 17 | +local function helper() |
| 18 | + return "I am hidden" |
| 19 | +end |
| 20 | + |
| 21 | +-- 3. 定义公开变量/函数 (绑定到 M) |
| 22 | +M.version = "1.0" |
| 23 | + |
| 24 | +function M.say_hello(name) |
| 25 | + -- 可以访问私有变量 |
| 26 | + return "Hello " .. name .. ", scale: " .. default_scale |
| 27 | +end |
| 28 | + |
| 29 | +function M.get_helper() |
| 30 | + return helper() |
| 31 | +end |
| 32 | + |
| 33 | +-- 4. 返回模块表 |
| 34 | +return M |
| 35 | +``` |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## 2. 调用模块 |
| 40 | + |
| 41 | +```lua |
| 42 | +-- main.lua |
| 43 | +local mymod = require("mymodule") -- 这里的字符串对应文件名(不带.lua) |
| 44 | + |
| 45 | +print(mymod.version) -- 输出: 1.0 |
| 46 | +print(mymod.say_hello("Lua")) |
| 47 | +-- print(mymod.helper()) -- 报错/nil,因为 helper 是 local 的 |
| 48 | +``` |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +## 3. 文件目录与路径管理 |
| 53 | + |
| 54 | +Lua 使用点号 `.` 来分隔目录,使用 `package.path` 来查找文件。 |
| 55 | + |
| 56 | +### 目录结构示例 |
| 57 | + |
| 58 | +```text |
| 59 | +project/ |
| 60 | +├── main.lua |
| 61 | +├── config.lua |
| 62 | +└── utils/ |
| 63 | + ├── init.lua <-- 特殊文件 |
| 64 | + ├── math.lua |
| 65 | + └── string.lua |
| 66 | +``` |
| 67 | + |
| 68 | +### 引用方式 |
| 69 | + |
| 70 | +```lua |
| 71 | +local conf = require("config") -- 加载 config.lua |
| 72 | +local umath = require("utils.math") -- 加载 utils/math.lua |
| 73 | +local utils = require("utils") -- 加载 utils/init.lua (类似 index.js) |
| 74 | +``` |
| 75 | + |
| 76 | +### 什么是 `init.lua`? |
| 77 | + |
| 78 | +当 `require("folder_name")` 时,Lua 会尝试查找 `folder_name/init.lua`。这允许你将一个文件夹作为一个整体模块导出。 |
| 79 | + |
| 80 | +**utils/init.lua 示例:** |
| 81 | + |
| 82 | +```lua |
| 83 | +local M = {} |
| 84 | +M.math = require("utils.math") |
| 85 | +M.string = require("utils.string") |
| 86 | +return M |
| 87 | +``` |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +## 4. 面向对象风格 (Class Module) |
| 92 | + |
| 93 | +如果你需要模块作为一个“类”来生成实例: |
| 94 | + |
| 95 | +```lua |
| 96 | +-- person.lua |
| 97 | +local Person = {} |
| 98 | +Person.__index = Person -- 元表索引指向自己 |
| 99 | + |
| 100 | +-- 构造函数 |
| 101 | +function Person.new(name, age) |
| 102 | + local self = setmetatable({}, Person) |
| 103 | + self.name = name |
| 104 | + self.age = age |
| 105 | + return self |
| 106 | +end |
| 107 | + |
| 108 | +-- 成员方法 (使用 : 语法) |
| 109 | +function Person:speak() |
| 110 | + print("My name is " .. self.name) |
| 111 | +end |
| 112 | + |
| 113 | +return Person |
| 114 | +``` |
| 115 | + |
| 116 | +**使用:** |
| 117 | + |
| 118 | +```lua |
| 119 | +local Person = require("person") |
| 120 | +local p1 = Person.new("Alice", 30) |
| 121 | +p1:speak() |
| 122 | +``` |
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +## 5. 高级技巧与坑点 |
| 127 | + |
| 128 | +### 避免全局污染 (Global Pollution) |
| 129 | + |
| 130 | +**错误写法:** |
| 131 | + |
| 132 | +```lua |
| 133 | +-- mymodule.lua |
| 134 | +function GlobalFunc() end -- 糟糕!这会污染全局环境 _G |
| 135 | +``` |
| 136 | + |
| 137 | +**正确写法:** |
| 138 | +始终在变量和函数前加 `local`,或者显式赋值给模块表 `M`。 |
| 139 | + |
| 140 | +### 循环依赖 (Circular Dependencies) |
| 141 | + |
| 142 | +如果 A require B,且 B require A,会导致栈溢出或返回 nil。 |
| 143 | + |
| 144 | +* **解决:** 将公共部分提取到模块 C,或者在一个模块内部通过 `local` 延迟加载。 |
| 145 | + |
| 146 | +### 重新加载模块 (Hot Reloading) |
| 147 | + |
| 148 | +Lua 默认会缓存模块在 `package.loaded` 中,再次 `require` 不会重新执行文件。 |
| 149 | +如果开发中需要热重载: |
| 150 | + |
| 151 | +```lua |
| 152 | +function reload_module(module_name) |
| 153 | + package.loaded[module_name] = nil |
| 154 | + return require(module_name) |
| 155 | +end |
| 156 | +``` |
| 157 | + |
| 158 | +### 添加自定义搜索路径 |
| 159 | + |
| 160 | +如果你的模块不在标准路径下: |
| 161 | + |
| 162 | +```lua |
| 163 | +-- 在 require 之前添加 |
| 164 | +package.path = package.path .. ";./libs/?.lua;./src/?.lua" |
| 165 | +``` |
| 166 | + |
| 167 | +--- |
| 168 | + |
| 169 | +## 6. 几种常见的写法变体 |
| 170 | + |
| 171 | +### 变体 A: 尾部返回 (最常用) |
| 172 | + |
| 173 | +```lua |
| 174 | +local M = {} |
| 175 | +function M.foo() end |
| 176 | +return M |
| 177 | +``` |
| 178 | + |
| 179 | +### 变体 B: 局部函数导出 (性能稍好) |
| 180 | + |
| 181 | +这种写法在文件内部调用 `foo` 时稍微快一点(因为是 local 调用),最后统一导出。 |
| 182 | + |
| 183 | +```lua |
| 184 | +local function foo() end |
| 185 | +local function bar() end |
| 186 | + |
| 187 | +return { |
| 188 | + foo = foo, |
| 189 | + bar = bar |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +### 变体 C: 直接返回函数 (单一职责) |
| 194 | + |
| 195 | +如果模块只做一件事: |
| 196 | + |
| 197 | +```lua |
| 198 | +-- logger.lua |
| 199 | +return function(msg) |
| 200 | + print("[LOG]: " .. msg) |
| 201 | +end |
| 202 | + |
| 203 | +-- 使用 |
| 204 | +local log = require("logger") |
| 205 | +log("Something happened") |
| 206 | +``` |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## 7. 模块查找顺序 |
| 211 | + |
| 212 | +当执行 `require("mod")` 时,Lua 按以下逻辑查找: |
| 213 | + |
| 214 | +1. 检查 `package.loaded["mod"]` 是否已有缓存。 |
| 215 | +2. 检查 `package.preload["mod"]` 是否有预加载器。 |
| 216 | +3. 搜索 `package.path` (Lua 文件)。 |
| 217 | + * `mod.lua` |
| 218 | + * `mod/init.lua` |
| 219 | +4. 搜索 `package.cpath` (C 库 .so/.dll)。 |
| 220 | + |
| 221 | +--- |
| 222 | + |
| 223 | +## 总结最佳实践 |
| 224 | + |
| 225 | +1. **文件即模块**:一个文件对应一个模块。 |
| 226 | +2. **Local First**:所有变量默认 `local`,只有需要导出的才放入返回表。 |
| 227 | +3. **返回 Table**:文件末尾始终 `return M`。 |
| 228 | +4. **无副作用**:`require` 一个模块不应产生打印日志或修改全局变量等副作用,只应返回定义。 |
0 commit comments