Lua 基礎之模塊

模塊

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

發佈了100 篇原創文章 · 獲贊 59 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章