模块
模块系统的设计目标是可以使用不同的方式来共享代码,一个模块就是一个代码库,其它模块 可以使用 require 函数来加载,加载后得到模块导出的所有东西,如函数、常量、全局变量等。一个比较好的方式是让模块返回一个 table 并保存在一个全局变量中,然后外部模块直接使用这个全局变量来操作 table
模块定义–mod.lua
demo = {}
demo.var = 100
demo.foo = function()
print("hello lua")
end
return demo
使用模块–main.lua
local m = require("mod")
m.foo()
demo.foo()
print(m.var, demo.var)
require 函数
require 函数用于加载一个模块,其实现细节如下
function require(name)
if not package.loaded[name] then
local loader = findloader(name)
if loader == nil then
error("unable load module")
end
package.loaded[name] = true
local res = loader(name)
if res ~= nil then
package.loader[name] = res
end
end
return package.loaded[name]
end
- 当加载一个模块时,会先在表 package.loaded 中查找该模块是否已经加载,如果已经加载了直接返回上次加载后的结果,因此重复调用 require 来加载同一个模块是不会出问题的
- 如果模块未加载,则试着为模块查找一个加载器,这个过程是在表 package.preload 中查询模块名,如果在其中找到一个函数则把该函数作为模块的加载器,上面代码使用 findloader 来抽象这个过程
- 接下来就是使用加载器来加载模块了,加载之前先把 package.loaded[name] 的值设为 true 这样可以避免两个模块交叉引用时陷入死循环;加载完成后把模块返回的值保存下来,如果模块没有显式地返回某个值,则返回的结果为 true
- 最后返回 package.laoder[name] 的值
编写模块的方法
返回一个 table
complex = {}
complex.__index = complex
complex.new = function(self, r, i)
local o = {}
setmetatable(o, self)
o._r = r or 0
o._i = i or 1
return o
end
complex.add = function(self, c1, c2)
return complex:new(c1._r + c2._r, c1._i + c2._i)
end
complex.get_r = function(self)
return self._r
end
complex.get_i = function(self)
return self._i
end
complex.set_r = function(self, v)
self._r = v
end
complex.set_i = function(self, v)
self._i = v
end
complex.i = complex:new(0, 1)
return complex
使用 require 函数加载模块后会得到一个全局变量 complex,它的值就是模块定义的 table,通过 complex 变量就能访问和操作整个模块,这是模块最基本的写法
模块定义与使用分离
上面的例子中模块定义直接使用导出的模块名,这种方式看起来不是很规范,一种改进方法是将模块定义与导出的模块名分离,可以在模块内使用局部来定义 table,再将局部变量的值赋给导出变量
local M = {}
M.__index = M
function M:new(r, i)
local ins = {}
setmetatable(ins, self)
ins._r = r or 0
ins._i = i or 1
return ins
end
function M:add(other)
return M:new(self._r + other:get_r(), self._i + other:get_i())
end
function M:get_i()
return self._i
end
function M:get_r()
return self._r
end
function M:set_i(v)
self._i = v
end
function M:set_r(v)
self._v = v
end
M.i = M:new(0, 1)
complex = M
return complex
自动模块名 & 隐藏返回值
local M = {}
local mod_name = ...
_G[mod_name] = M
package.loaded[mod_name] = M
M.__index = M
function M:new(r, i)
local ins = {}
setmetatable(ins, self)
ins._r = r or 0
ins._i = i or 1
return ins
end
function M:add(other)
return M:new(self._r + other:get_r(), self._i + other:get_i())
end
function M:get_i()
return self._i
end
function M:get_r()
return self._r
end
function M:set_i(v)
self._i = v
end
function M:set_r(v)
self._v = v
end
M.i = M:new(0, 1)
_G[mod_name] = M
创建了一个全局变量 mod_name = M
模块没显式写返回值,默认会返回 package.loaded[mod_name]
使用环境
现在定义模块都是在全局环境中完成时,这时不管数据的定义还是使用都得加上对应的前缀,而且如果定义私有变量时忘记加上 local 就会污染到全局环境。函数环境 让模块独占一个环境,这个环境与全局环境完全隔离,此时对模块而言,全局环境就是它自己的环境,因此它不能访问到真正的全局环境,也就是说不能访问其它基本模块,如果要使用全局环境中的模块,可以在使用 函数环境 前把这些模块保存到局部变量
使用 函数环境 后函数和变量的定义和使用都不需要加上模块前缀了
lua5.1 之前使用 函数环境 的方式是 setfenv(1,mod)
,5.3 的方式是 ENV = mod
local M = {}
-- 模块名
local mod_name = ...
-- 保存到全局变量
_G[mod_name] = M
-- 保存到已加载模块
package.loaded[mod_name] = M
local setmetatable = setmetatable
-- 使用“函数环境”
-- setfenv(1, M) -- 5.1
local _ENV = M -- 5.3
__index = M
new = function(self, r, i)
local o = {}
setmetatable(o, self)
o._r = r or 0
o._i = i or 1
return o
end
add = function(self, other)
return new(M, self._r + other._r, self._i + other._i)
end
get_i = function(self)
return self._i
end
get_r = function(self)
return self._r
end
set_i = function(self, v)
self._i = v
end
set_r = function(self, v)
self._v = v
end
module 函数
使用 module 函数可以简化上面的模块定义
module(...)
等价于
local mod_name = ...
_G[mod_name] = M
package.loaded[mod_name] = M
setfenv(1, M)
如果想访问原来的全局环境,则使用
module(... , package.seeall)
注意: 这是 lua5.1 之前定义模块的方法,5.3 并不建议使用 函数环境,这种方式会定义一个该模块的 table,然后注入到全局环境,这样虽然模块内的东西不会污染到全局环境,但整个模块会污染到全局环境,因为没有引用该模块的文件也能访问该模块内的函数或变量