经常让你感到困惑的是,当外部模块被“需要”到你的代码中时到底发生了什么,这导致了在 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.lua
,scene1.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
属性存在!我们可以从中了解到什么?继续阅读……
原因是example1.lua 已加载
main.lua
加载。从本质上讲,当加载(需要)一个模块时,它的代码将从上到下照常执行,并且该模块的返回值将存储在一个名为 package.loaded
的全局表中。从那时起,该模块被认为是“已加载的”,它不能被
让我们更详细地探讨一下这个问题
当你调用 require()
时,首先发生的事情是 package.loaded
表被检查以查看该模块是否先前加载过。
如果是的话,那么就不会package.loaded
中存储的返回值将被返回。换而言之,此返回值是一个引用,而不是一个副本。因此,如果你的模块返回一个表,当你将来对同一个模块调用 require()
时,你将获得相同的表(包括属性)。
如果没有在 package.loaded
中找到该模块,那么该模块将被加载,该代码将从上到下运行,并且该模块的返回值将被存储在 package.loaded
表中以供将来需要该模块时使用。这解释了为什么example1.lua 已加载
example1.lua
已在 main.lua
中需要,它不是scene1.lua
中。
可以在模块内获取代码的方法有两种
将代码放入函数中(与模块同级)并像正常函数一样调用。
从 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.lua
和 scene1.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 模块、如何包含模块以及如何从一个模块调用存在于另一个模块中的函数有了坚实的基础性理解。值得庆幸的是,这些规则是通用的,适用于你可能遇到的任何模块,无论是你自己