Bảng Metatables

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Bảng Metatables cho phép các bảng trở nên mạnh mẽ hơn trước. Chúng được gắn với dữ liệu và chứa các giá trị gọi là metamethods. Các metamethods được gọi khi một hành động nhất định được sử dụng với dữ liệu mà nó được gắn vào.

Thao tác với metatables

Hai hàm chính để thêm và tìm bảng metatable là setmetatable()getmetatable().


local x = {}
local metaTable = {} -- Metatables cũng là bảng!
setmetatable(x, metaTable) -- Gán cho x một metatable gọi là metaTable!
print(getmetatable(x)) --> table: [địa chỉ bộ nhớ thập lục phân]

Hàm setmetatable() cũng trả về bảng mà bạn đang thiết lập metatable, vì vậy hai đoạn mã sau làm cùng một việc:


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

local x = setmetatable({}, {})

Metamethods

Metamethods là các hàm được lưu trữ bên trong một metatable. Chúng có thể thực hiện từ việc gọi một bảng, đến việc cộng một bảng, đến cả việc chia các bảng. Đây là danh sách các metamethods có sẵn:

Phương thứcMô tả
__index(table, index)Được gọi khi table[index] được chỉ mục, nếu table[index]nil. Cũng có thể được gán cho một bảng, trong trường hợp đó bảng đó sẽ được chỉ mục.
__newindex(table, index, value)Được gọi khi table[index] cố gắng được thiết lập (table[index] = value), nếu table[index]nil. Cũng có thể được gán cho một bảng, trong trường hợp đó bảng đó sẽ được chỉ mục.
__call(table, ...)Được gọi khi bảng được gọi như một hàm, ... là các đối số đã được truyền vào.
__concat(table, value)Được gọi khi toán tử nối .. được sử dụng trên bảng.
__unm(table)Được gọi khi toán tử đơn được sử dụng trên bảng.
__add(table, value)Toán tử cộng +.
__sub(table, value)Toán tử trừ .
__mul(table, value)Toán tử nhân *.
__div(table, value)Toán tử chia /.
__idiv(table, value)Toán tử chia làm tròn //.
__mod(table, value)Toán tử modulus %.
__pow(table, value)Toán tử lấy lũy thừa ^.
__tostring(table)Được gọi khi tostring được gọi trên bảng.
__metatableNếu có, khóa metatable nên getmetatable() sẽ trả về điều này thay vì metatable và setmetatable() sẽ lỗi. Giá trị không phải hàm.
__eq(table, value)Toán tử bằng ==¹
__lt(table, value)Toán tử nhỏ hơn <¹
__le(table, value)Toán tử nhỏ hơn hoặc bằng <=¹
__modeĐược sử dụng trong các bảng yếu, tuyên bố xem các khóa và/hoặc giá trị của một bảng có yếu hay không. Lưu ý rằng các tham chiếu đến các đối tượng của Roblox sẽ không bao giờ yếu. Các bảng giữ các tham chiếu như vậy sẽ không bao giờ bị thu gom rác.
__len(table)Được gọi khi toán tử # chiều dài được sử dụng trên đối tượng.
__iter(table)Được sử dụng để chỉ định một bộ lặp tùy chỉnh khi sử dụng việc lặp lại tổng quát.

Cần lưu ý rằng khi viết các hàm cho bất kỳ metamethods số học hay quan hệ nào, hai tham số hàm có thể hoán đổi giữa bảng đã kích hoạt metamethod và giá trị khác. Ví dụ, khi thực hiện các phép toán vector với các số vô hướng, phép chia không có tính giao hoán. Do đó, nếu bạn đang viết các metamethods cho lớp vector2 của riêng mình, bạn nên cẩn thận để tính đến cả hai trường hợp.


local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a là một số vô hướng, b là một vector
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a là một vector, b là một số vô hướng
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
-- cả a và b đều là vectors
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

Cách sử dụng

Có nhiều cách để sử dụng metatables, ví dụ như metamethod __unm để làm một bảng âm:


local metatable = {
__unm = function(t) -- __unm là cho toán tử đơn -
local negated = {}
for key, value in t do
negated[key] = -value -- phủ định tất cả các giá trị trong bảng này
end
return negated -- trả về bảng
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12

Đây là một cách thú vị để khai báo bằng cách sử dụng __index:


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

__index đã được gọi khi x được chỉ mục trong bảng và không tìm thấy. Luau sau đó đã tìm kiếm trong bảng __index một chỉ mục có tên là x, và, khi tìm thấy, đã trả về chỉ mục đó.

Bây giờ bạn có thể dễ dàng thực hiện điều đó với một hàm đơn giản, nhưng còn nhiều hơn thế nữa. Lấy ví dụ này:


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

Thông thường, bạn không thể gọi một bảng, nhưng với metatables, bạn có thể:


local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Cộng tham số (5) với giá trị và sau đó đặt nó vào bảng mới (t).
end
return unpack(sum) -- Trả về các giá trị bảng riêng lẻ
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35

Bạn cũng có thể làm nhiều điều hơn nữa, chẳng hạn như cộng các bảng:


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

Điều này sẽ gây lỗi nói rằng bạn đang cố gắng thực hiện phép toán trên một bảng, nhưng nó hoạt động khi thử nghiệm với một metatable:


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

Khi chơi với metatables, bạn có thể gặp phải một số vấn đề. Nếu bạn cần sử dụng metamethod __index để tạo các giá trị mới trong bảng, nhưng metatable của bảng đó cũng có một metamethod __newindex, bạn sẽ muốn sử dụng hàm tích hợp sẵn của Luau rawset() để thiết lập giá trị mà không gọi bất kỳ metamethod nào. Lấy đoạn mã sau làm ví dụ về những gì xảy ra nếu bạn không sử dụng các hàm này.


local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- Đừng đặt giá trị cho bảng theo cách thông thường
end
})
print(t[1]) -- Gây ra tràn ngăn xếp

Tràn ngăn xếp xảy ra khi bạn cố gắng gọi một hàm từ chính nó quá nhiều lần. Trong hàm __index ở trên, self[i] được thiết lập với một giá trị, vì vậy khi nó đến dòng tiếp theo, self[i] nên tồn tại và có lẽ sẽ không gọi metamethod __index. Vấn đề là __newindex không cho phép bạn thiết lập giá trị. Sự hiện diện của nó ngăn cản các giá trị được thêm vào bảng bằng phương pháp chuẩn t[i] = v. Để vượt qua điều này, hãy sử dụng hàm rawset():


local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- Đừng đặt giá trị cho bảng theo cách thông thường
end
})
print(t[1]) --> 10

Sử dụng kiểu dữ liệu tập hợp

Một tập hợp là một tập hợp các mục không có thứ tự và không có các phần tử trùng lặp. Một mục hoặc không có trong một tập hợp. Bằng cách sử dụng metatables, bạn có thể xây dựng và thao tác các tập hợp trong các kịch bản Luau.

Phương thức cơ bản

Đoạn mã sau bao gồm các chức năng cơ bản của tập hợp, cho phép bạn xây dựng các tập hợp mới, thêm và xóa một mục, kiểm tra xem một tập hợp có chứa một mục hay không, và xuất nội dung của một tập hợp.


local Set = {}
Set.__index = Set
-- Hàm để xây dựng một tập hợp từ một danh sách tùy chọn các mục
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Hàm để thêm một mục vào một tập hợp
function Set:add(item)
self[item] = true
end
-- Hàm để xóa một mục khỏi một tập hợp
function Set:remove(item)
self[item] = nil
end
-- Hàm để kiểm tra xem một tập hợp có chứa một mục hay không
function Set:contains(item)
return self[item] == true
end
-- Hàm để xuất tập hợp dưới dạng danh sách phân cách bằng dấu phẩy cho việc gỡ lỗi
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end

Tạo tập hợp

Một tập hợp mới có thể được xây dựng bằng cách gọi Set.new() với một mảng tùy chọn các mục để thêm.


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

Lưu ý rằng theo định nghĩa, một tập hợp không có khái niệm về thứ tự.

Thêm mục

Thêm một mục vào một tập hợp hiện có có thể được thực hiện qua phương thức Set:add().


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

Xóa mục

Để xóa một mục khỏi một tập hợp, gọi Set:remove() với tên mục.


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

Kiểm tra mục

Để kiểm tra xem một tập hợp có chứa một mục cụ thể hay không, sử dụng 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

Phương thức bổ sung

Các phép toán hữu ích khác có thể được triển khai cho các tập hợp, cho phép bạn so sánh các mục giữa các tập hợp, kết hợp các tập hợp, hoặc trừ một tập hợp khỏi tập hợp khác.

Giao nhau

Khi xem xét các tập hợp như sơ đồ Venn, bạn có thể có giao nhau của hai tập hợp như sau, nghĩa là các mục xuất hiện trong cả hai tập hợp.


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

Hợp nhất

Bạn có thể có hợp nhất của hai tập hợp với hàm sau, nghĩa là một tập hợp các mục có trong cả hai tập hợp mà không có các mục trùng lặp. Lưu ý rằng hàm này sử dụng phương thức metatable __add để cung cấp một phím tắt cộng 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

Phép trừ

Bạn có thể loại bỏ tất cả các mục trong một tập hợp khỏi các mục trong một tập hợp khác thông qua hàm sau. Tương tự như hàm ở trên, hàm này sử dụng phương thức metatable __sub để cung cấp một phím tắt trừ 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, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.