一.Lua中的模块(module)
模块官方的定义
Programming in Lua(第四版)中对于模块的定义:
From the point of view of the user, a module is some code (either in Lua or in C) that can be loaded throughthe function require and that creates and returns a table. Everything that the module exports, such asfunctions and constants, it defines inside this table, which works as a kind of namespace.
从用户的角度来看,模块是一段代码(可以用 Lua 或 C 编写),可以通过 `require` 函数加载, 并创建一个表返回**。**模块导出的所有内容,例如函数和常量,都定义在这个表中,该表起到命名空间的作用。
精简结论:模块是可以通过require函数加载的Lua或C文件,eg:

包(package)是模块的集合
二.require函数
require函数的作用是加载一个模块,有一个参数:模块名称。require的括号是可选的:
Lua
require('M') 等价于 require 'M'
2.1 require工作原理
require执行时,eg:
Lua
require("module1")
第一步是检查package.loaded.module1是否存在,
若不存在,如下图所示

则加载module1(module1.lua的代码从上到下执行),

require执行完成后,会将返回值赋值给package.loaded.module1。

若已存在,直接返回package.loaded.module1;

特殊情况:模块没有返回值时,require返回true,并将package.loaded.module1赋值为true,从而后续再次require该模块的时候,和有返回值一致,不会再次加载该模块
Lua
local M = {}
local index = 0
function M.Func()
index = index + 1
print("calling M.Func", index)
end
-- return M

要 require 加载同一个模块两次,可以从 package.loaded 中删除该库条目,这点非常有用,比如你修改了Lua文件,想在不重启游戏的情况下让其生效,只需要执行package.loaded.module1 = nil后,再次require就可以了
Lua
package.loaded.module1 = nil
require("module1")
2.2 require搜索路径
package.path保存require使用的搜索路径,每个路径是一个包含可选问号的模板,模板中的问号会被替换成模块名。模板用分号分割,从第一个模板开始,若没找到模块则访问第二个模板。eg:
Lua
package.path = ".\?.lua;.\?\init.lua"
下面展示若干example:在Assets/TestRequire/A.lua中调用require,访问B.lua
A.lua
Lua
local B = require("B")
local M = {lv = 15, name = "A"}
return M
B.lua
Lua
local M = {lv = 22, name = "B"}
return M
1.使用绝对路径
package.path包含module的绝对路径,?替换module名
路径分隔符推荐用正斜杠/,也可用双反斜杠\\,但反斜杠\被视作转义字符,不可用
Lua
package.path = "D:/U3DP/?.lua;" --正确:正斜杠
--package.path = "D:\\U3DP\\?.lua;" --正确:双反斜杠
-- package.path = "D:\U3DP\?.lua;" --错误:单反斜杠视作转义字符
local B = require("B")
local M = {lv = 15, name = "A"}
return M

2. package.path匹配全部失败时会报错

3.按目录匹配
package.path保存的模板中,?不仅可用作文件名占位,也可用作目录占位:
Lua
package.path = "D:/U3DP/?/init.lua;"
local B = require("B")
local M = {lv = 15, name = "A"}
return M

4.使用相对目录
package.path = "?.lua" 代表在当前工作目录替换问号,当前工作目录可通过下面的get_current_dir函数获取,将B放到该工作目录后,成功被 A require到
Lua
local function get_current_dir()
local handle
if package.config:sub(1,1) == '\\' then
-- Windows
handle = io.popen("cd")
else
-- Linux/Mac
handle = io.popen("pwd")
end
local result = handle:read("*a")
handle:close()
return result:gsub("\n", "")
end
print("当前工作目录: " .. get_current_dir())
package.path = "?.lua"
local B = require("B")
local M = {lv = 15, name = "A"}
return M

5.使用子模块
require的参数可以包含路径名,在不改动package.path的情况下,require到指定文件。比如A.B,在搜索时会被转化成A/B,点号会被转化成/。
Lua
package.path = "?.lua"
local B = require("A.B")
local M = {lv = 15, name = "A"}
return M

三.子模块和包
Lua 允许模块名称具有层级结构,使用点号分隔不同的名称级别。例如,名为 mod.sub 的模块是 mod 的子模块。包是模块的完整树状结构,也是 Lua 中的分发单元。require子模块时按以下方式执行:eg:require("mod.sub")
├─ 1. package.loaded"mod.sub" 有吗? → 有则直接返回
├─ 2. package.preload"mod.sub" 有吗? → 有则直接返回
└─ 3. 把 "mod.sub" 转成 "mod/sub"(或 mod\sub)
按 package.path 查找文件
例如:mod/sub.lua
找到后执行文件,返回值存入 package.loaded"mod.sub"