【具有默認值的table】
我們都知道,table中的任何字段的默認值都是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 這里打印的就是默認值
可以看到,在代碼中,setDefault函數為所有需要默認值的table創建了一個新的元表。如果准備創建很多需要默認值得table,這種方法的開銷或許就比較大了。由於在元表中默認值defaultValue是與元方法關聯在一起的,所以setDefault無法為所有table都使用同一個元表。如果要讓具有不同默認值得table都使用同一個元表,那么就需要將每個元表的默認值存放在table本身中,可以使用一個額外的字段來存儲默認值。例如以下代碼:
local mt = {__index = function (t) return t.___ end} function setDefault(tb, defaultValue) tb.___ = defaultValue -- 非常謝謝hellowei犀利的review。具體請參見評論 setmetatable(tb, mt) end
上面代碼中的“___”是為了防止名字沖突而起的名字;如果這樣的話,你還擔心名字沖突,確保key在table中的唯一性,只需要創建一個新的table,並用它作為key即可,每一個新創建的table都是一個唯一的地址,比如以下代碼:
local key = {} -- 唯一的key local mt = {__index = function (tb) return tb[key] end} function setDefault(tb, defaultValue) tb[key] = defaultValue setmetatable(tb, mt) end
【記錄table的訪問】
有的時候,一種特定的需求,我們需要記錄對一個table的所有訪問,不管是查詢還是更新,我們都需要記錄日志。這如何完成?我們都知道,元表中的__index和__newindex是在table中沒有所需要訪問的index時才發揮作用的,因此,只有將一個table保持為空,然后設置__index和__newindex元方法,才有可能記錄下來所有對它的訪問。
為了監視一個table的所有訪問,就應該為真正的table創建一個代理。這個代理就是一個空的table,其中__index和__newindex元方法可用於跟蹤所有的訪問,並將訪問重定義到原來的table上。這就是思路,接下來看代碼:
local t = {} --原來的table -- 保持對原table的一個引用 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
如果想要同時監視幾個table,無須為每個table創建不同的元表;相反,只要以某種形式將每個代理與其原table關聯起來,並且所有代理都共享一個公共的元表。這個問題與設置table默認值相關聯的問題類似,也是將原來的table保存在代理table的一個特殊的字段中。代碼如下:
-- 創建唯一索引 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)
【 只讀的table】
通過代理的概念,可以很容易的實現只讀的table。只需要跟蹤所有對table的更新操作,並引發一個錯誤就好了,對於查詢時,我們不用去館,只需要管對table的更新操作,廢話不說,來段簡單的代碼,自然而然的一目了然了。
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]) tbDemo[1] = 20
元表中__index對應的是原來的table,而更新原來的table時,就會顯示錯誤提示:Attempt to update a read-only table。