__index和__newindex都是只有当表中访问的域不存在时候才起作用。捕获对一个表的所有访问情况的唯一方法就是保持表为空。因此,如果我们想监控一个表的所有访问情况,我们应该为真实的表创建一个代理。这个代理是一个空表,并且带有__index和__newindex metamethods,由这两个方法负责跟踪表的所有访问情况并将其指向原始的表。假定,t是我们想要跟踪的原始表,我们可以:
t = {} -- original table (created somewhere) -- keep a private access to original table local _t = t -- create proxy t = {} -- create metatable local mt = { __index = function (t,k) print("*access to element " .. tostring(k)) return _t[k] -- access the original table end, __newindex = function (t,k,v) print("*update of element " .. tostring(k) .. " to " .. tostring(v)) _t[k] = v -- update original table end } setmetatable(t, mt)
这段代码将跟踪所有对t的访问情况:
> t[2] = 'hello' *update of element 2 to hello > print(t[2]) *access to element 2 hello
(注意:不幸的是,这个设计不允许我们遍历表。Pairs函数将对proxy进行操作,而不是原始的表。)
如果我们想监控多张表,我们不需要为每一张表都建立一个不同的metatable。我们只要将每一个proxy和他原始的表关联,所有的proxy共享一个公用的metatable即可。将表和对应的proxy关联的一个简单的方法是将原始的表作为proxy的域,只要我们保证这个域不用作其他用途。一个简单的保证它不被作他用的方法是创建一个私有的没有他人可以访问的key。将上面的思想汇总,最终的结果如下:
-- create private index local index = {} -- create metatable local mt = { __index = function (t,k) print("*access to element " .. tostring(k)) return t[index][k] -- access the original table end __newindex = function (t,k,v) print("*update of element " .. tostring(k) .. " to " .. tostring(v)) t[index][k] = v -- update original table end } function track (t) local proxy = {} proxy[index] = t setmetatable(proxy, mt) return proxy end
现在,不管什么时候我们想监控表t,我们要做得只是t=track(t)。