加入我们要跟踪对某个表的所有访问。由于 __index
和 __newindex
元方法都是在表中的索引不存在的时候才有用,因此,捕获对一个表所有访问的唯一方式是保持表是空的。如果要监控对一个表的所有访问,那么需要为真正的表创建一个代理( proxy
)。这个代理是一个空的表,具有用于跟踪所有访问并将访问重定向到原来的表的合理方法。
function track (t) local proxy = {} -- ‘t’的代理表 -- 为代理创建元表 local mt = { __index = function (_, k) print("*access to element " .. tostring(k)) return t[k] -- 访问原来的表 end, __newindex = function (_, k, v) print("*update of element " .. tostring(k) .. " to " .. tostring(v)) t[k] = v -- 更新原来的表 end, __pairs = function () return function (_, k) -- 迭代函数 local nextkey, nextvalue = next(t, k) if nextkey ~= nil then -- 避免最后一个值 print("*traversing element " .. tostring(nextkey)) end return nextkey, nextvalue end end, __len = function () return #t end } setmetatable(proxy, mt) return proxy end
以下展示了上述代码的用法:
> t = {} > t = track(t) > t[2] = "hello" --> *update of element 2 to hello > print(t[2]) --> *access to element 2 --> hello
元方法 __index
和 __newindex
按照我们设计的规则跟踪每一个访问并将其重定向到原来的表中。元方法 __pairs
使得我们能够像遍历原来的表一样遍历代理,从而跟踪所有的访问。最后,元方法 __len
通过代理实现了长度操作符:
t = track({10, 20}) print(#t) --> 2 for k, v in pairs(t) do print(k, v) end --> *traversing element 1 --> 1 10 --> *traversing element 2 --> 2 20
如果想要同时监控几个表,并不需要为每个表创建不同的元表。相反,只要以某种形式将每个代理与其原始表映射起来,并且让所有的代理共享一个公共的元表即可。这个问题与把表与其默认值关联起来的问题类似,因此可以采用相同的解决方式。例如,可以把原来的表保存在代理表的一个特殊的字段中,或者使用一个对偶表示建立代理与相应表的映射。