table的跟蹤
本文跟蹤table,是指對一個table 的操作,比如訪問和更新進行跟蹤。當訪問一個 table 或者更新 table 中的某個元素時,lua 首先會在 table 查找是否存在該元素,如果沒有,就會查找 table 是否存在 __index(訪問)
或者 __newindex(更新)
原方法。以訪問爲例,首先在 table 中查找某個字段,如果不存在,解釋器會去查找 __index
這個原方法,如果仍然沒有,返回 nil。所以說,__index
和 __newindex
是在 table 中沒有所需訪問的 index 時才發揮作用的。
根據上面這種思路,如果我們想跟蹤一個 table 的操作行爲,那麼需要一個空表,每次對這個空表操作的時候,就會使用 __index
或者 __newindex
這些元方法,在元方法中對原始 table 進行訪問和操作,並打印跟蹤信息。而之前創建的那個空表,就是代理。
對單個 table 的跟蹤
先創建一個代理表,爲空表,然後創建 metatable
local _t = orignal_table -- 對原表的一個私有訪問,原表在其他地方創建的
local proxy = {}
local mt = {
__index = function (proxy, k)
print("access to element " .. tostring(k))
return _t[k] -- 這裏訪問的其實就是原表中的元素了
end,
__newindex = function (proxy, k, v)
print("update of element " .. tostring(k) .. " to " .. tostring(v))
_t[k] = v -- 更新原表中的元素
end
}
setmetatable(proxy, mt)
-- 操作,觀察結果
proxy[2] = "lua"
print(proxy[2])
結果爲:
update of element 2 to lua
access to element 2
lua
對多個table的跟蹤
對多個table的跟蹤,思路與上面相同,只需要按照某種形式將每個代理與原表 table 關聯起來,並且所有的代理都共享一個公共的元表 metatable,比如將原表 table 保存在代理 table 的一個特殊字段中
proxy[index] = t -- t 就是原表 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) .. " to " .. tostring(v))
t[index][k] = v
end
}
function track (t)
local proxy = {}
proxy[index] = t
setmetatable(proxy, mt)
return proxy
end
-- ori_table = {} 在其他地方創建的原表,對他進行跟蹤
local _o = track(ori_table)
_o[2] = "lua"
print(_o[2])
觀察輸出結果,與上面的相同
只讀 table
只讀 table,通過上面的代理的思想也很容易實現,對元方法 __index
,訪問自身,對更新操作的元方法 __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
因爲 proxy 代理 table 是一個空表,當訪問時,解釋器通過元方法 __index
訪問,這個元方法此處就是一個 table ,就是原表 table ,直接返回原表 table 中的字段。如果更新字段,解釋器將使用元方法 __newindex
進行更新,觸發一個錯誤,不允許更新,並說明這是隻讀的 table。
參考文獻:
1. 《Programming In Lua》