尽管函数 require
也只是一个没什么特殊之处的普通函数,但在 Lua
语言的模块实现中扮演着核心角色。要加载模块时,只需要简单地调用这个函数,然后传入模块名作为参数。请记住,当函数的参数只有一个字符串常量时括号是可以忽略的,而且一般在使用 require
时按照惯例也会省略括号。不过尽管如此,下面的这些用法也是正确的:
local m = require("math") local modname = "math" local m = require(modname)
函数 require
尝试对模块的定义做最小的假设。对于该函数来说,一个模块可以是定义了一些变量(比如函数或者包含函数的表达式)的代码。典型地,这些代码返回一个由模块中函数组成的表。不过,由于这个动作是有模块的代码而不是由函数 require
完成的,所以某些模块可能会选择返回其他的值或者甚至引发副作用(例如,通过创建全局变量)。
首先,函数 require
在表 package.loaded
中检查模块是否已被加载。如果模块已经被加载,函数 require
就返回相应的值。因此,一旦一个模块被加载过,后续的对于同一模块的所有 require
调用都将返回同一个值,而不会再运行任何代码。
如果模块尚未加载,那么函数 require
则会搜索具有指定模块名的 Lua
文件(搜索路径由变量 package.path
指定,我们会在后续对其进行讨论)。如果函数 require
找到了相应的文件,那么就用函数 loadfile
将其进行加载,结果是一个我们称之为加载器的函数(加载器就是一个被调用时加载模块的函数)。
如果函数 require
找不到指定模块名的 Lua
文件,那么它就搜索相应名称的 C
标准库(在这种情况下,搜索路径由变量 package.cpath
指定)。如果找到了一个 C
标准库,则使用底层函数 package.loadlib
进行加载,这个底层函数会查找名为 luaopen_modname
的函数。在这种情况下,加载函数就是 loadlib
的执行结果,也就是一个被表示为 Lua
函数的 C
语言函数 luaopen_modname
。
不管模块是在 Lua
文件还是 C
标准库中找到的,函数 require
此时都具有了用于加载它的加载函数。为了最终加载模块,函数 require
带着两个参数调用加载函数:模块名和加载函数所在文件的名称(大多数模块会忽略这两个参数)。如果加载函数有返回值,那么函数 require
会返回这个值,然后将其保存在表 package.loaded
中,以便与将来在加载同一模块时返回相同的值。如果加载函数没有返回值且表中的 package.loaded[@rep{modname}]
为空,函数 require
就假设模块的返回值是 true
。如果没有这种补偿,那么后续调用函数 require
时将会重复加载模块。
强制函数 require
加载同一模块两次,可以先将模块从 package.loaded
中删除:
package.loaded.modname = nil
下一次再加载这个模块时,函数 require
就会重新加载模块。
对于函数 require
来说,一个常见的抱怨就是它不能给待加载的模块传递参数。例如,数学模块可以对角度和弧度的选择增加一个选项:
-- 错误的代码 local math = require("math", "degree")
这里的问题在于,函数 require
的主要目的之一就是避免重复加载模块。一旦一个模块被加载,该模块就会在后续所有调用 require
的程序部分被复用。这样,不同参数的同名模块之间就会产生冲突。如果真的需要具有参数的模块,那么最好使用一个显式的函数来设置参数,例如:
local mod = require "mod" mod.init(0, 0)
如果加载函数返回的是模块本身,那么还可以写成:
local mod = require "mod".init(0, 0)
注意
模块在任何情况下只加载一次,至于如何处理冲突的加载,取决于模块自己。