模塊
模塊系統的設計目標是可以使用不同的方式來共享代碼,一個模塊就是一個代碼庫,其它模塊 可以使用 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,然後注入到全局環境,這樣雖然模塊內的東西不會污染到全局環境,但整個模塊會污染到全局環境,因爲沒有引用該模塊的文件也能訪問該模塊內的函數或變量