在 Lua 程式設計中,元表與元方法是實現進階功能的核心機制。其中 __index 與 __newindex 兩個元方法更是讓我們能夠攔截表的讀取與寫入操作,從而實現預設值、存取日誌、唯讀表等強大特性。本文將透過實際程式碼範例,帶領讀者徹底掌握這兩個元方法的運作原理與應用技巧。
Lua 表的欄位預設值皆為 nil,但透過元表我們可以輕鬆改變這項規則。最直觀的做法是為每個需要預設值的表建立專屬元表:
function setDefault(tb, defaultValue)
local mt = {__index = function() return defaultValue end}
setmetatable(tb, mt)
end
local tb1 = {x = 10, y = 20}
print(tb1.x, tb1.z) -- 輸出: 10 nil
setDefault(tb1, 100)
print(tb1.x, tb1.z) -- 輸出: 10 100 (這裡取得的是預設值)
不過,若需建立大量帶預設值的表,上述方法會產生大量元表,造成記憶體開銷。優化策略是讓所有表共用同一個元表,將預設值儲存在表自身的特殊欄位中:
local mt = {__index = function(t) return t.___ end}
function setDefault(tb, defaultValue)
tb.___ = defaultValue -- 使用不易衝突的鍵名
setmetatable(tb, mt)
end
為進一步確保鍵名唯一,可採用「建立新表作為鍵」的技巧:
local key = {} -- 唯一的 key
local mt = {__index = function(tb) return tb[key] end}
function setDefault(tb, defaultValue)
tb[key] = defaultValue
setmetatable(tb, mt)
end
若需監控對表的所有查詢與更新,關鍵在於 __index 與 __newindex 僅在表中找不到對應鍵時才會被觸發。因此,我們需建立一個「代理表」——一個空表,透過元方法將所有存取導向原始表:
local t = {} -- 原始表
local _t = t -- 保留對原表的引用
-- 建立代理表
t = {}
-- 設定元表
local mt = {
__index = function(t, k)
print("access to element " .. tostring(k))
return _t[k]
end,
__newindex = function(t, k, v)
print("update of element " .. tostring(k))
_t[k] = v
end
}
setmetatable(t, mt)
t.x = 10 -- 輸出: update of element x
print(t.x) -- 輸出: access to element x \n 10
同樣地,若要同時監控多個表,可讓所有代理共用同一元表,並透過唯一索引關聯原表:
local index = {} -- 唯一索引
local mt = {
__index = function(t, k)
print("access to element " .. tostring(k))
return t[index][k]
end,
__newindex = function(t, k, v)
print("update of element " .. tostring(k))
t[index][k] = v
end
}
function track(t)
local proxy = {}
proxy[index] = t
setmetatable(proxy, mt)
return proxy
end
local t = {}
local proxy = track(t)
proxy.x = 10
print(proxy.x)
利用代理模式,唯讀表的實現極為簡潔:僅需在 __newindex 中拋出錯誤即可:
function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(t, k, v)
error("Attempt to update a read-only table", 2)
end
}
setmetatable(proxy, mt)
return proxy
end
local tbDemo = readOnly{1, 2, 3, 4, 5}
print(tbDemo[1]) -- 正常輸出 1
tbDemo[1] = 20 -- 拋出錯誤: Attempt to update a read-only table
__index 與 __newindex 是 Lua 元編程的基石,掌握它們能解鎖預設值注入、存取審計、資料保護等多種進階模式。元表機制讓 Lua 在保持輕量的同時,具備了極高的擴展性,是編寫高階庫與框架的關鍵工具。建議讀者動手實驗上述範例,體會「代理表」與「共用元表」的設計精髓。