1. 啥是元表,为啥需要元表
在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。
所以通俗的理解就是:元表就是为“一个方法表(类似函数表)“,里面包含了一些解决方案。当一个table设置元表之后,相当于关联了这个方法表
2. 表相关的元方法
__index 用于对表的访问,相当于一个获取的属性的get 函数。lua 查找表元素的时候其流程总结如下:
* 1 在表中查找
>>找到了,返回该元素
>>找不到,继续
*2 判断是否有元表
>>没有元表,返回nil
>>有元表,继续
* 3 判断是否有__index方法
>>__index为nil, 返回nil
>>__index为表,则重复1,2,3 的步骤
>>__index为函数,把table和键值当作参数传递给函数,然后返回这个函数的返回值
__newindex用于对表的更新,相当于设置属性的set 函数。所以给一个table的属性赋值的的流程如下:
*1 查找tabie 中是否有这个字段 >> 有,更新该字段 >>没有,检查是否有元表 ,并且元表是否有__newindex方法>> 没有,则给table 添加该字段,>>有,判断__newindex 的值>>如果__newindex是一个函数,则在给table不存在的函数赋值的时候,会调用这个函数>>如果__newindex是一个表,则在给table不存在字段赋值的时候,则会直接给__newindex的table赋值。
今天被问个问题,如何让一个表的属性不被修改,就是利用__newindex,设置元素
metatable.__newindex= function(t,key,value) print(“设置了不能被修改”)end 一时没想起来,故作此记录下,下面是简单的实现:
function read_only_tb( tb_b )
local tb_a = {}
local tb_c = {}
tb_c.__index = tb_b
tb_c.__newindex = function ( t,k,v )
print("该表设置了不能被修改")
end
setmetatable(tb_a,tb_c)
return tb_a
end
测试代码和结果:
---测试
local tb_b = {a= 1,b= 2,c= 3}
local tb_proxy = read_only_tb(tb_b)
print("获取只读属性:",tb_proxy.a)
tb_proxy.a = 12
上面只是比较简单的测试,只读table的实现原理,如果要真正应用,那么可能考虑的情况会更多,比目标table本身就设置了元表, 表中的字段可能又是一个table等等。所以整理的比较完整的设置配置的table 为只读的实现:
function read_only(inputTable)
local travelled_tables = {}
local function __read_only(tbl)
if not travelled_tables[tbl] then
local tbl_mt = getmetatable(tbl)
if not tbl_mt then
tbl_mt = {}
setmetatable(tbl, tbl_mt)
end
local proxy = tbl_mt.__read_only_proxy
if not proxy then
proxy = {}
tbl_mt.__read_only_proxy = proxy
local proxy_mt = {
__index = tbl,
__newindex = function (t, k, v) error("error write to a read-only table with key = " .. tostring(k)) end,
__pairs = function (t) return pairs(tbl) end,
-- __ipairs = function (t) return ipairs(tbl) end, 5.3版本不需要此方法
__len = function (t) return #tbl end,
__read_only_proxy = proxy
}
setmetatable(proxy, proxy_mt)
end
travelled_tables[tbl] = proxy
for k, v in pairs(tbl) do
if type(v) == "table" then
tbl[k] = __read_only(v)
end
end
end
return travelled_tables[tbl]
end
return __read_only(inputTable)
end