2.表引用与共享
2.1 知识点
引用语义回顾
我们学习过 table 的基本用法。
table 直接赋值,不会复制出一张新表。
local a = { id = 1 }
local b = a
这段代码里只有一张表。a 和 b 是两个变量,它们指向同一张表。
b.id = 2
print(a.id) -- 输出:2
print(b.id) -- 输出:2
如果直接改表字段,两个变量都会看到变化。重点是:拿到的是独立数据,还是同一张表的另一个入口。
改字段和换引用
改字段和换引用是两种不同语义。
local a = { hp = 100 }
local b = a
b.hp = 80
print(a.hp) -- 输出:80
b.hp = 80 改的是原表字段。
只要还握着这张表,都会看到这个变化。
换引用则不会动旧表:
local a = { hp = 100 }
local b = a
b = { hp = 999 }
print(a.hp) -- 输出:100
print(b.hp) -- 输出:999
b 指向新表以后,a 还在旧表上。
参数传的是引用值
Lua 参数是按值传递。
但传进去的值如果是 table,传的就是地址。
local role = { hp = 100 }
local function Hurt(target)
target.hp = target.hp - 20
end
Hurt(role)
print(role.hp) -- 输出:80
函数里改字段,改到的是外面那张表。
给参数重新赋值则不会影响外部变量:
local role = { hp = 100 }
local function Reset(target)
target = { hp = 999 }
end
Reset(role)
print(role.hp) -- 输出:100
target 只是函数里的局部变量。
它换到新表,不会把外面的 role 一起换掉。
配置表和运行时表
配置表通常是静态共享数据。
如果业务直接拿配置改,容易污染原始配置。
local SkillConfig = {
[1001] = {
id = 1001,
name = "FireBall",
damage = 100
}
}
local cfg = SkillConfig[1001]
cfg.damage = cfg.damage + 50
print(SkillConfig[1001].damage) -- 输出:150
这里改的是配置本身。
项目里一般会把配置和运行时数据拆开:
local SkillConfig = {
[1001] = {
id = 1001,
name = "FireBall",
damage = 100
}
}
local cfg = SkillConfig[1001]
local runtimeSkill = {
id = cfg.id,
name = cfg.name,
damage = cfg.damage,
level = 1
}
runtimeSkill.damage = runtimeSkill.damage + 50
print(runtimeSkill.damage) -- 输出:150
print(SkillConfig[1001].damage) -- 输出:100
配置只负责给初始值。
战斗过程、Buff 加成、临时修正,这些都必须要有运行时对象。
类表字段共享
Lua 里用表和元表模拟类时,类表上的 table 字段要注意,看下面这个case:
local PlayerClass = {
buffs = {}
}
local playerA = {}
local playerB = {}
setmetatable(playerA, { __index = PlayerClass })
setmetatable(playerB, { __index = PlayerClass })
table.insert(playerA.buffs, "SpeedUp")
print(#playerA.buffs) -- 输出:1
print(#playerB.buffs) -- 输出:1
playerB 没加 Buff,两个实例都通过 __index 读到 PlayerClass.buffs,有点像静态变量。
但是实际上运行时实例状态应该放在实例表里:
local playerA = { buffs = {} }
local playerB = { buffs = {} }
table.insert(playerA.buffs, "SpeedUp")
print(#playerA.buffs) -- 输出:1
print(#playerB.buffs) -- 输出:0
否则可能一改改到了“静态”成员。
旧引用残留
旧引用可能在回调、缓存、模块返回值、协程里被捕获。
local function CreateHpGetter(target)
return function()
return target.hp
end
end
local role = { hp = 100 }
local getHp = CreateHpGetter(role)
role = { hp = 999 }
print(role.hp) -- 输出:999
print(getHp()) -- 输出:100
上面的例子里,外部 role 已经换成新表,回调捕获的 target 还指着旧表。UI 按钮、事件、计时器、协程里都可能残留。
2.2 知识点代码
Lesson2_表引用与共享.lua
print("**********表引用与共享************")
print("**********知识点一 引用语义回顾************")
local a = { id = 1 }
local b = a
b.id = 2
print(a.id) -- 输出:2
print(b.id) -- 输出:2
print("**********知识点二 改字段和换引用************")
local dataA = { hp = 100 }
local dataB = dataA
dataB.hp = 80
print(dataA.hp) -- 输出:80,改字段会影响原表
dataB = { hp = 999 }
print(dataA.hp) -- 输出:80,dataA 还指向旧表
print(dataB.hp) -- 输出:999,dataB 已经指向新表
print("**********知识点三 参数传的是引用值************")
local role = { hp = 100 }
local function Hurt(target)
target.hp = target.hp - 20
end
local function Reset(target)
target = { hp = 999 }
end
Hurt(role)
print(role.hp) -- 输出:80,函数里改字段会影响原表
Reset(role)
print(role.hp) -- 输出:80,函数里换引用不会替换外部 role
print("**********知识点四 配置表别当运行时表改************")
local BadSkillConfig = {
[1001] = {
id = 1001,
name = "FireBall",
damage = 100
}
}
local badCfg = BadSkillConfig[1001]
badCfg.damage = badCfg.damage + 50
print(BadSkillConfig[1001].damage) -- 输出:150,原始配置被改脏
local SkillConfig = {
[1001] = {
id = 1001,
name = "FireBall",
damage = 100
}
}
local cfg = SkillConfig[1001]
local runtimeSkill = {
id = cfg.id,
name = cfg.name,
damage = cfg.damage,
level = 1
}
runtimeSkill.damage = runtimeSkill.damage + 50
print(runtimeSkill.damage) -- 输出:150,只改运行时数据
print(SkillConfig[1001].damage) -- 输出:100,配置表保持原值
print("**********知识点五 类表字段共享************")
local PlayerClass = {
buffs = {}
}
local playerA = {}
local playerB = {}
setmetatable(playerA, { __index = PlayerClass })
setmetatable(playerB, { __index = PlayerClass })
table.insert(playerA.buffs, "SpeedUp")
print(#playerA.buffs) -- 输出:1
print(#playerB.buffs) -- 输出:1,两个实例读到同一张类表字段
local fixedPlayerA = { buffs = {} }
local fixedPlayerB = { buffs = {} }
table.insert(fixedPlayerA.buffs, "SpeedUp")
print(#fixedPlayerA.buffs) -- 输出:1
print(#fixedPlayerB.buffs) -- 输出:0,实例状态已经分开
print("**********知识点六 旧引用藏在回调里************")
local function CreateHpGetter(target)
return function()
return target.hp
end
end
local oldRole = { hp = 100 }
local getHp = CreateHpGetter(oldRole)
oldRole = { hp = 999 }
print(oldRole.hp) -- 输出:999,外部变量已经换到新表
print(getHp()) -- 输出:100,回调还握着旧表
print("**********知识点七 总结************")
print("配置表默认只读,运行时状态单独建")
print("类表字段别当实例状态")
print("热更排查旧回调、旧闭包、旧协程")
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com