Menu
CoddyTech

__newindex 元方法

本课属于 Coddy Lua 学习旅程的“逻辑与流程”单元。第 19 课,共 54 课。

你已经学会了 __index 如何控制你尝试读取表中不存在的键时会发生什么。现在,你将了解它的对应项:__newindex,它控制你给表中尚不存在的键赋值时会发生什么。

每当你尝试给表中当前不存在的键赋值时,都会触发 __newindex 元方法。这样,你就可以在修改发生前拦截并控制它。

下面是一个简单的示例,它可以防止向表中添加任何新键:

local readOnly = {}
local meta = {
    __newindex = function(table, key, value)
        print("Cannot add key: " .. key)
    end
}

setmetatable(readOnly, meta)

readOnly.name = "Alice"  -- 输出:Cannot add key: name
print(readOnly.name)     -- 输出:nil

当你尝试设置 readOnly.name = "Alice" 时,Lua 会发现表中不存在 name。Lua 不会添加它,而是调用 __newindex 函数,并传入 3 个参数:表本身、要设置的键,以及要赋的值。

在这个示例中,函数只会打印一条消息,并不会实际存储任何内容。

你也可以使用 __newindex,在允许数据进入表之前先验证数据:

local player = {}
local meta = {
    __newindex = function(t, key, value)
        if key == "health" and type(value) ~= "number" then
            print("Health must be a number!")
        else
            rawset(t, key, value)
        end
    end
}

setmetatable(player, meta)

player.health = "high"  -- 输出:Health must be a number!
player.health = 100     -- 允许赋值,用 rawset 存储
player.health = "high"  -- 没有提示:health 现在已存在

注意第二个示例中使用了 rawset()。这个函数会绕过元表,直接在表中设置值。

没有它,设置值会再次触发 __newindex,从而造成无限循环。

因为 __newindex 只会在表中不存在某个键时运行,所以这个检查会验证每个键的首次赋值。rawset 存储了 health 后,后续赋值会直接修改它。若要检查每次赋值,可以让表本身保持为空,并将值存储在第二个表中,再通过 __index 读取这些值:

local data = {}
local player = setmetatable({}, {
    __index = data,
    __newindex = function(t, key, value)
        if key == "health" and type(value) ~= "number" then
            print("Health must be a number!")
        else
            data[key] = value
        end
    end
})

player.health = 100
player.health = "high"  -- 输出:Health must be a number!
print(player.health)    -- 输出:100

player 本身从未获得任何键,因此每次赋值都会经过 __newindex,每次读取都会通过 __index 访问 data。

challenge icon

挑战

简单

编写一个函数 createValidatedTable,它接收 key 和 value,并返回一个只接受新键对应的字符串值的表。

创建一个空表,并为它附加一个元表,其中的 __newindex 会验证值的类型。如果有人尝试添加非字符串值,就使用 rawset() 存储字符串 "invalid"。如果值是字符串,则使用 rawset() 正常存储。

逻辑:

  • 创建一个空表
  • 创建一个元表,其中的 __newindex 接收表、键和值
  • 在 __newindex 中,使用 type(value) == "string" 检查值是否为字符串
  • 如果值是字符串,使用 rawset(t, key, value) 存储它
  • 如果值不是字符串,使用 rawset(t, key, "invalid") 存储 "invalid" 作为替代值
  • 使用 setmetatable() 将元表附加到表上
  • 使用 rawset() 将初始键值对添加到表中(绕过元表)
  • 返回该表

参数:

  • key(字符串):要添加到表中的初始键
  • value(字符串):要添加到表中的初始值

返回值:一个经过验证、只接受字符串值且包含初始键值对的表(table)

亲手试试

function createValidatedTable(key, value)
    -- 在这里编写代码
end
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

逻辑与流程中的所有课

自己练习: Lua 在线编译器