使用外部模块

经常让你感到困惑的是,当外部模块被“需要”到你的代码中时到底发生了什么,这导致了在 Composer 场景,甚至是自己的自定义模块等方面产生进一步的困惑和意外的行为。

本教程将通过一系列练习(以及注释)指导你,这些练习(及注释)旨在准确阐述模块在 Lua 中是如何工作的,以便你全面了解何时(以及哪个)代码在你的模块中执行。

包括外部模块

在其最简单的形式中,外部模块只是一个返回某些内容(最有可能是一个表)的 Lua 文件。它几乎可以被认为是一个函数定义,但它本身包含在一个文件中。

这里有一个非常简单的模块 example1.lua,它只是向控制台打印一个语句并返回一个空表

-- example1.lua
 
local M = {}

print( "example1.lua has been loaded" )

return M

现在,在不同的模块(我们只使用 main.lua)中需要 example1.lua 模块并查看发生了什么(请注意,本教程中的所有代码示例都假定自定义外部模块被放置在项目文件夹的根级别,以及 main.lua)。

-- main.lua
 
local ex1 = require( "example1" )

ex1.testvar = "Hello World!"

正如预期的那样,在执行第一个命令后,变量 ex1 变为我们创建并在 example1.lua 中返回的空表 (M),并且单词example1.lua 已加载出现在控制台中。

在下一条命令中,我们赋值一个平凡的属性 testvar 到该 ex1 表。

现在仔细注意这一点,因为它变得更加棘手了。让我们需要在另一个模块中需要 example1.luascene1.lua我们已经在 main.lua 中需要它之后

-- scene1.lua (previous "main.lua" still applies)

local examp1 = require( "example1" )

print( examp1.testvar )

这一次,在 scene1.lua 中,当你需要 example1.lua 模块时,以下单词example1.lua 已加载出现在控制台中。但是,当你打印 examp1.testvar 的值时,你好,世界!输出,这意味着 testvar 属性存在!我们可以从中了解到什么?继续阅读……

“package.loaded” 表

原因是example1.lua 已加载出现在控制台中,是因为该模块已被 main.lua 加载。从本质上讲,当加载(需要)一个模块时,它的代码将从上到下照常执行,并且该模块的返回值将存储在一个名为 package.loaded 的全局表中。从那时起,该模块被认为是“已加载的”,它不能被重新加载通过再次需要它来刷新。

让我们更详细地探讨一下这个问题

执行模块代码

可以在模块内获取代码的方法有两种

  1. 将代码放入函数中(与模块同级)并像正常函数一样调用。

  2. package.loaded 表格中删除模块,然后重新加载(通常不推荐)。

下面是第一个方案的示例

-- example2.lua
 
local M = {}

print( "example2.lua has been loaded" )
 
M.hello = function()
    print( "Hello World!" )
end

return M
-- main.lua

local ex2 = require( "example2" )  --> example2.lua has been loaded

ex2.hello()  --> Hello World!
-- scene1.lua

local examp2 = require( "example2" )

examp2.hello()  --> Hello World!

如你所见,我们在两个不同的模块中加载了 example2.lua,分别是 main.luascene1.lua。第一个 print() 语句(在任何函数之外的语句)只出现一次(首次在 main.lua 中加载模块时)。相比之下,第二个 print() 语句(位于 M.hello 函数内的语句)在每次调用该函数时都会执行。

从中我们学到了什么?实际上,如果你想在模块中多次运行代码,或者不想在加载模块时立即运行它,你应该将其封装在函数中,并将该函数“附加”到你在模块末尾返回的表格中。

重要须知

将模块函数正确“附加”到返回的表格至关重要,以便访问它们。你不能简单地将该函数作为局部函数包含在模块中,然后期望从另一个模块访问它。注意区别

local M = {}
 
-- This function WILL be accessible outside of this module, wherever the module is required
M.hello = function()
    print( "Hello World!" )
end

-- This function will NOT be accessible outside of this module, only within it locally
local hello = function()
    print( "Hello World!" )
end

return M

移除模块

正如上面所述,你需要使用以下语法加载模块,例如 example2.lua

local ex2 = require( "example2" )

这样一来,example2.lua 的返回值实际上会存储在以下位置的 package.loaded 表格中

package.loaded["example2"]

因此,如果你想“取消加载”模块并从内存中完全清除它,只需将 nil 分配给该引用,如下所示

package.loaded["example2"] = nil

此时,模块将从内存中清除,其所有关联函数将停止存在。

结束

希望本教程能让你对 Lua 模块、如何包含模块以及如何从一个模块调用存在于另一个模块中的函数有了坚实的基础性理解。值得庆幸的是,这些规则是通用的,适用于你可能遇到的任何模块,无论是你自己定制的模块,你下载的模块,甚至内置Solar2D 模块。