第一版是為 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以協助 支援 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)
這個技巧需要更多工作,但它能更好地記錄你的套件依賴關係。它也會產生比前一個方案更快的程式碼。