Lua 基础之模块

来源:互联网 发布:oracle查看数据库名称 编辑:程序博客网 时间:2024/06/04 20:03

模块

模块系统的设计目标是可以使用不同的方式来共享代码,一个模块就是一个代码库,其它模块 可以使用 require 函数来加载,加载后得到模块导出的所有东西,如函数、常量、全局变量等。一个比较好的方式是让模块返回一个 table 并保存在一个全局变量中,然后外部模块直接使用这个全局变量来操作 table

模块定义–mod.lua

demo = {}demo.var = 100demo.foo = function()    print("hello lua")endreturn 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 = complexcomplex.new = function(self, r, i)    local o = {}    setmetatable(o, self)    o._r = r or 0    o._i = i or 1    return oendcomplex.add = function(self, c1, c2)    return complex:new(c1._r + c2._r, c1._i + c2._i)endcomplex.get_r = function(self)    return self._rendcomplex.get_i = function(self)    return self._iendcomplex.set_r = function(self, v)    self._r = vendcomplex.set_i = function(self, v)    self._i = vendcomplex.i = complex:new(0, 1)return complex

使用 require 函数加载模块后会得到一个全局变量 complex,它的值就是模块定义的 table,通过 complex 变量就能访问和操作整个模块,这是模块最基本的写法

模块定义与使用分离

上面的例子中模块定义直接使用导出的模块名,这种方式看起来不是很规范,一种改进方法是将模块定义与导出的模块名分离,可以在模块内使用局部来定义 table,再将局部变量的值赋给导出变量

local M = {}M.__index = Mfunction M:new(r, i)    local ins = {}    setmetatable(ins, self)    ins._r = r or 0    ins._i = i or 1    return insendfunction M:add(other)    return M:new(self._r + other:get_r(), self._i + other:get_i())endfunction M:get_i()    return self._iendfunction M:get_r()    return self._rendfunction M:set_i(v)    self._i = vendfunction M:set_r(v)    self._v = vendM.i = M:new(0, 1)complex = Mreturn complex

自动模块名 & 隐藏返回值

local M = {}local mod_name = ..._G[mod_name] = Mpackage.loaded[mod_name] = MM.__index = Mfunction M:new(r, i)    local ins = {}    setmetatable(ins, self)    ins._r = r or 0    ins._i = i or 1    return insendfunction M:add(other)    return M:new(self._r + other:get_r(), self._i + other:get_i())endfunction M:get_i()    return self._iendfunction M:get_r()    return self._rendfunction M:set_i(v)    self._i = vendfunction M:set_r(v)    self._v = vendM.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] = Mlocal setmetatable = setmetatable-- 使用“函数环境”-- setfenv(1, M)    -- 5.1local _ENV = M      -- 5.3__index = Mnew = function(self, r, i)    local o = {}    setmetatable(o, self)    o._r = r or 0    o._i = i or 1    return oendadd = function(self, other)    return new(M, self._r + other._r, self._i + other._i)endget_i = function(self)    return self._iendget_r = function(self)    return self._rendset_i = function(self, v)    self._i = vendset_r = function(self, v)    self._v = vend

module 函数

使用 module 函数可以简化上面的模块定义

module(...)

等价于

local mod_name = ..._G[mod_name] = Mpackage.loaded[mod_name] = Msetfenv(1, M) 

如果想访问原来的全局环境,则使用
module(... , package.seeall)
注意: 这是 lua5.1 之前定义模块的方法,5.3 并不建议使用 函数环境,这种方式会定义一个该模块的 table,然后注入到全局环境,这样虽然模块内的东西不会污染到全局环境,但整个模块会污染到全局环境,因为没有引用该模块的文件也能访问该模块内的函数或变量

原创粉丝点击