第一版是针对 Lua 5.0 编写的。虽然对于后续版本来说仍然具有很大的相关性,但还是有一些区别。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还将帮助 支持 Lua 项目


15.4 – 使用全局表

创建包的所有这些方法的一个缺点是,它们需要程序员特别注意。例如,很容易忘记在声明中使用 local。全局变量表中的元方法提供了一些有趣的替代技术来创建包。所有这些技术中的共同点是为包使用一个独占环境。这很容易做到:如果我们更改包的主块的环境,它创建的所有函数都将共享这个新环境。

最简单的技术几乎不比这多做。一旦包拥有一个独占环境,不仅其所有函数都共享此表,而且其所有全局变量都进入此表。因此,我们可以将所有公共函数声明为全局变量,它们将自动进入一个单独的表。包所要做的就是将此表注册为包名称。以下代码片段说明了针对 complex 库的此技术

    local P = {}
    complex = P
    setfenv(1, P)
现在,当我们声明函数 add 时,它将进入 complex.add
    function add (c1, c2)
      return new(c1.r + c2.r, c1.i + c2.i)
    end
此外,我们可以不带任何前缀调用此包中的其他函数。例如,add 从其环境中获取 new,即它获取 complex.new

此方法为包提供了良好的支持,而程序员几乎不需要额外的工作。它根本不需要前缀。调用导出的函数和私有函数之间没有区别。如果程序员忘记了 local,她不会污染全局命名空间;相反,只有私有函数会变为公共函数。此外,我们可以将其与上一部分中有关包名称的技术结合使用

    local P = {}   -- package
    if _REQUIREDNAME == nil then
      complex = P
    else
      _G[_REQUIREDNAME] = P
    end
    setfenv(1, P)

当然,缺少的是对其他包的访问。一旦我们把空表P作为我们的环境,我们就失去了对所有先前全局变量的访问。对此有几种解决方案,每种解决方案都有其优点和缺点。

最简单的解决方案是继承,如我们之前所见

    local P = {}   -- package
    setmetatable(P, {__index = _G})
    setfenv(1, P)
(你必须在调用setfenv之前调用setmetatable;你能说出原因吗?)使用此构造,该包可以直接访问任何全局标识符,但每次访问都会支付少量开销。这种解决方案的一个有趣后果是,从概念上讲,你的包现在包含所有全局变量。例如,使用你的包的人可能会通过编写complex.math.sin(x)来调用标准正弦函数。(Perl 的包系统也有这个特点。)

访问其他包的另一个快速方法是声明一个包含旧环境的局部变量

    local P = {}
    pack = P
    local _G = _G
    setfenv(1, P)
现在,你必须使用_G.为对外部名称的任何访问添加前缀,但你可以获得更快的访问速度,因为没有涉及元方法。与继承不同,这种方法让你可以写访问旧环境;这是否好坏有待商榷,但有时你可能需要这种灵活性。

一种更有条理的方法是仅将你需要的函数或最多将你需要的包声明为局部变量

    local P = {}
    pack = P
    
    -- Import Section:
    -- declare everything this package needs from outside
    local sqrt = math.sqrt
    local io = io
    
    -- no more external access after this point
    setfenv(1, P)
这种技术需要更多的工作,但它可以更好地记录你的包依赖项。它还可以生成比以前方案更快的代码。