元表

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

元表使表變得比以往更強大。它們附加到數據上,並包含稱為元方法的值。當某個操作與附加的數據一起使用時,元方法會被觸發。

操作元表

添加和查找表的元表的兩個主要函數是 setmetatable()getmetatable()

local x = {}
local metaTable = {} -- 元表也是表!
setmetatable(x, metaTable) -- 給 x 一個名為 metaTable 的元表!
print(getmetatable(x)) --> table: [十六進制內存地址]

setmetatable() 函數還會返回你正在設置元表的表,因此這兩個腳本做的是相同的事情:

local x = {}
setmetatable(x, {})
local x = setmetatable({}, {})

元方法

元方法是存儲在元表中的函數。它們可以用於調用表、添加表,甚至除法操作。以下是可用的元方法列表:

方法描述
__index(table, index)table[index] 被索引且 table[index]nil 時觸發。也可以設置為一個表,在這種情況下該表將被索引。
__newindex(table, index, value)table[index] 嘗試被設置 (table[index] = value)table[index]nil 時觸發。也可以設置為一個表,在這種情況下該表將被索引。
__call(table, ...)當表像函數一樣被調用時觸發,... 是傳遞的參數。
__concat(table, value)當在表上使用 .. 串接運算符時觸發。
__unm(table)當在表上使用一元 運算符時觸發。
__add(table, value)+ 加法運算符。
__sub(table, value) 減法運算符。
__mul(table, value)* 乘法運算符。
__div(table, value)/ 除法運算符。
__idiv(table, value)// 向下取整除法運算符。
__mod(table, value)% 取模運算符。
__pow(table, value)^ 指數運算符。
__tostring(table)當對表調用 tostring 時觸發。
__metatable如果存在,鎖定元表,使 getmetatable() 返回此值而不是元表,並且 setmetatable() 會報錯。非函數值。
__eq(table, value)== 等於運算符¹
__lt(table, value)< 小於運算符¹
__le(table, value)<= 運算符¹
__mode用於弱表,聲明表的鍵和/或值是否為弱。參見 使用弱表
__len(table)當對對象使用 # 長度運算符時觸發。
__iter(table)用於在使用廣義迭代時表示自定義迭代器。

應注意的是,當為算術或關係元方法編寫函數時,兩個函數參數在觸發元方法的表和其他值之間是可互換的。例如,在進行向量操作時,與標量的除法不是交換的。因此,如果你正在為自己的 vector2 類編寫元方法,你需要小心考慮這兩種情況。

local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a 是標量,b 是向量
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a 是向量,b 是標量
local vector, scalar = a, b
return vector2.new(vector.x / scalar, vector.y / scalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- a 和 b 都是向量
return vector2.new(a.x / b.x, a.y / b.y)
end
end
function mt.__tostring(t)
return t.x .. ", " .. t.y
end
function vector2.new(x, y)
local self = setmetatable({}, mt)
self.x = x or 0
self.y = y or 0
return self
end
local a = vector2.new(10, 5)
local b = vector2.new(-3, 4)
print(a / b) -- -3.3333333333333, 1.25
print(b / a) -- -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5

用法

有很多方法可以使用元表。

對表應用一元負號運算符

__unm 元方法處理一元負號運算符。用它來否定表中的每個值:

local metatable = {
__unm = function(t) -- __unm 是一元 - 運算符
local negated = {}
for key, value in t do
negated[key] = -value -- 否定此表中的所有值
end
return negated -- 返回表
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12

從另一個表索引缺失的值

這是一種使用 __index 聲明事物的有趣方法:

local metatable = {
__index = {x = 1}
}
local t = setmetatable({}, metatable)
print(t.x) --> 1

當在表中索引 x 並未找到時,__index 被觸發。Luau 然後在 __index 表中查找名為 x 的索引,並找到後返回該值。

像函數一樣調用表

元表可以做的不僅僅是提供缺失的值。舉個例子:

local t = {10, 20, 30}
print(t(5))

通常你不能調用一個表,但使用元表你可以:

local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- 將參數 (5) 加到值上,然後放入新表 (t) 中。
end
return unpack(sum) -- 返回單獨的表值
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35

對表應用加法運算符

你還可以做更多的事情,例如添加表:

local table1 = {10, 11, 12}
local table2 = {13, 14, 15}
for k, v in table1 + table2 do
print(k, v)
end

這將報錯,因為你嘗試對一個表進行算術運算,但在使用元表時它會正常工作:

local metatable = {
__add = function(t1, t2)
local sum = {}
for key, value in t1 do
sum[key] = value
end
for key, value in t2 do
if sum[key] then
sum[key] += value
else
sum[key] = value
end
end
return sum
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
local table2 = setmetatable({13, 14, 15}, metatable)
for k, v in table1 + table2 do
print(k, v)
end

在不調用元方法的情況下設置值

在操作元表時,你可能會遇到一些問題。如果你需要使用 __index 元方法在表中創建新值,但該表的元表也有 __newindex 元方法,你將希望使用 Luau 內置函數 rawset() 來設置值,而不調用任何元方法。以下代碼示例展示了如果不使用這些函數會發生什麼。

local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- 不以正常方式設置值到表中
end
})
print(t[1]) -- 導致堆棧溢出

堆棧溢出發生在你嘗試從自身調用函數太多次。在上面的 __index 函數中,self[i] 被設置為一個值,因此當它到達下一行時,self[i] 應該存在,並且理應不會調用 __index 元方法。問題是 __newindex 不允許你設置該值。它的存在阻止了使用標準 t[i] = v 方法將值添加到表中。為了克服這個問題,使用 rawset() 函數:

local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- 不以正常方式設置值到表中
end
})
print(t[1]) --> 10

使用弱表

持有表、函數、線程和 用戶數據 的變量,包括 Instances,存儲對這些對象的引用,而不是它們的副本。將一個對象分配給另一個變量會創建對同一對象的另一個引用:

local map = {key = 1}
local mapReference = map
print(map == mapReference) --> true
print(map == {key = 1}) --> false
local function func() end
local funcReference = func
print(func == funcReference) --> true
local part = Instance.new("Part")
local partReference = part
print(part == partReference) --> true

不再通過強引用可達的對象變為垃圾回收的候選。將一個變量設置為 nil 不會使對象成為垃圾回收的候選,如果另一個強引用仍然指向該對象:

local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [十六進制內存地址]

雖然 child 不再指向子表,但 parent.Child 仍然指向它。只要父表保持可達,它的引用就會使子表保持可達。

弱表 持有鍵、值或兩者,而不阻止垃圾回收器回收其他不可達對象。設置表的元表的 __mode 字段以控制哪些引用是弱的:

  • "k" 使鍵為弱。
  • "v" 使值為弱。
  • "kv""vk" 使鍵和值都為弱。

以下代碼示例創建一個具有弱值的表:

local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
end

do 塊結束後,存儲在 children.example 中的值沒有強引用,並變為垃圾回收的候選。當垃圾回收器回收子表時,Luau 會從 children 中刪除其條目。垃圾回收是非確定性的,因此不要依賴條目消失的時間。

你留下的任何模式都保持強。__mode = "v" 表中的鍵保持強引用,因此作為鍵存儲的對象保持可達,其條目保持在表中。

對於 Instances,垃圾回收適用於 Luau 引用,而不是對象本身。引擎將實例數據存儲在 Luau VM 之外,因此收集 Luau 引用不會刪除實例,而仍然在數據模型中的實例不會保持其 Luau 引用。要釋放實例,請調用 Instance:Destroy() 並清除對它的所有剩餘引用。

使用集合數據類型

集合 是一組沒有順序且沒有重複元素的項目。項目要麼 存在於 集合中,要麼 不在 集合中。使用元表,你可以在 Luau 腳本中構建和操作集合。

基本方法

以下代碼包括基本的集合功能,讓你構建新的集合、添加和移除項目、檢查集合是否包含某個項目,以及輸出集合的內容。

local Set = {}
Set.__index = Set
-- 從可選的項目列表構建集合的函數
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- 將項目添加到集合的函數
function Set:add(item)
self[item] = true
end
-- 從集合中移除項目的函數
function Set:remove(item)
self[item] = nil
end
-- 檢查集合是否包含某個項目的函數
function Set:contains(item)
return self[item] == true
end
-- 將集合輸出為逗號分隔的列表以進行調試的函數
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end

創建集合

可以通過調用 Set.new() 並傳遞可選的項目數組來構建新的集合。

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})

請注意,根據定義,集合沒有排序的概念。

添加項目

可以通過 Set:add() 方法將項目添加到現有集合中。

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")

移除項目

要從集合中移除項目,請調用 Set:remove() 並傳遞項目名稱。

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")

檢查項目

要檢查集合是否包含特定項目,請使用 Set:contains()

local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local result1 = fruits:contains("Cherry")
print(result1) -- true
local result2 = fruits:contains("Watermelon")
print(result2) -- false

附加方法

可以為集合實現其他有用的操作,讓你比較集合之間的項目、合併集合或從一個集合中減去另一個集合。

交集

當將集合視為維恩圖時,可以獲得兩個集合的 交集,即出現在 兩個 集合中的項目。

local function getIntersection(set1, set2)
local result = Set.new()
for key, value in set1 do
if set2:contains(key) then
result:add(key)
end
end
return result
end
local freshFruits = Set.new({"Mango", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local frozenFruits = Set.new({"Mango", "Peach", "Pineapple"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, Peach

聯集

你可以使用以下函數獲得兩個集合的 聯集,即兩個集合中所有項目的集合,且不包含重複項目。請注意,此函數使用元表的 __add 方法提供 set1 + set2 的加法快捷方式。

function Set:__add(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = true
end
return result
end
local sweetFruits = Set.new({"Apple", "Mango", "Cherry", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Peach, Lime, Apple, Cherry, Lemon, Mango

減法

你可以通過以下函數從一個集合中移除另一個集合中的所有項目。與上面的函數類似,這使用元表的 __sub 方法提供 set1 - set2 的減法快捷方式。

function Set:__sub(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = nil
end
return result
end
local allFruits = Set.new({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach
©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。