Mục lục

🌟 Metatable là gì?

Metatable (bảng siêu dữ liệu) trong Lua là các bảng đặc biệt cho phép bạn thay đổi cách các bảng thông thường hoạt động. Hãy nghĩ chúng như những “quy tắc” bạn có thể thiết lập để điều khiển điều gì xảy ra khi bạn thực hiện một số hành động nhất định với một bảng. Logic của nó rất đơn giản và hoàn toàn không gây nhầm lẫn.

regular_Table = {}
meta_rule = {} -- quy tắc của bạn ở đây
setmetatable(regular_Table, meta_rule)

Bây giờ bảng regular_table đã được thiết lập các phương thức meta sử dụng quy tắc trong meta_rule. Nhưng trong ví dụ này, ta chưa làm gì với bảng regular_table.

📌 Danh sách các quy tắc

Dưới đây là một số hành vi phổ biến mà bạn có thể thay đổi:

🛠️ Cách sử dụng & Ví dụ

Metatable thường được dùng cho framework hoặc tạo ra mã có thể tái sử dụng.

1. Truy cập một khóa không tồn tại _index

Bạn có thể định nghĩa điều gì xảy ra khi bạn cố truy cập một khóa không tồn tại trong bảng.

local myTable = {}
local meta = {
    __index = function(table, key)
        return "This key does not exist!"
    end
}
setmetatable(myTable, meta)
 
print(myTable.nonExistentKey)  -- Output: This key does not exist!

Từ ví dụ trên, bạn có thể chỉnh sửa mã để khi một khóa không tồn tại được truy cập, nó sẽ thực thi một hàm và trả về thông báo “This key does not exist!”.

Bạn cũng có thể chỉnh hàm này để thực hiện hành động cụ thể, ví dụ như thêm khóa với một giá trị mặc định.

2. Gán một cặp khóa-giá trị mới (_newindex)

Bạn có thể kiểm soát hành vi khi thêm một cặp khóa-giá trị mới vào bảng.

local myTable = {}
local meta = {
    __newindex = function(table, key, value)
        print("New key-value pair added:", key, value)
    end
}
setmetatable(myTable, meta)
 
myTable.newKey = "newValue"  -- Output: New key-value pair added: newKey newValue

Chú ý rằng quy tắc trong bảng meta_newindex, nó thiết lập quy tắc cho việc thêm chỉ số mới. Quy tắc này sẽ thực thi _newindex(table, key, value). Các tham số là cố định.

3. Các phép toán số học (_add, _sub, _mul, _div, _mod, _pow, _unm)

Bạn có thể định nghĩa hành vi tùy chỉnh cho các phép toán số học với bảng.

local table1 = {1, 2}
local table2 = {3, 4}
local meta = {
    __add = function(a, b)
        return {a[1] + b[1], a[2] + b[2]}
    end
}
setmetatable(table1, meta)
 
local result = table1 + table2
print(result[1], result[2])  -- Output: 4 6

Trong Lua, hành vi mặc định khi bạn thực hiện các phép toán số học (như cộng, trừ, nhân, v.v.) trên bảng là gây lỗi. Lua không hỗ trợ sẵn các phép toán trên bảng vì bảng không được thiết kế để dùng như các thực thể toán học.
Với metatable, bạn có thể thiết lập hành vi và phương thức khi thực hiện phép toán trên bảng, nhưng nó chỉ hoạt động với các metatable bạn đã định nghĩa.

4. Nối chuỗi (_concat)

Bạn có thể định nghĩa hành vi nối bảng tùy chỉnh.

local table1 = {1, 2}
local table2 = {3, 4}
local meta = {
    __concat = function(a, b)
        return table.concat(a, ",") .. "," .. table.concat(b, ",")
    end
}
setmetatable(table1, meta)
 
local result = table1 .. table2
print(result)  -- Output: 1,2,3,4

Mặc định, Lua không hỗ trợ nối bảng bằng toán tử … Thực hiện nối mà không định nghĩa hành vi sẽ gây lỗi.

5. Toán tử độ dài (_len)

Bạn có thể định nghĩa hành vi tùy chỉnh cho việc tính độ dài bảng.

local myTable = {1, 2, 3}
local meta = {
    __len = function(t)
        return 42  -- Một giá trị tuỳ ý
    end
}
setmetatable(myTable, meta)
 
print(#myTable)  -- Output: 42

Hành vi mặc định của toán tử # trong Lua là trả về độ dài phần mảng của bảng. Nó chỉ tính dãy liên tiếp không nil bắt đầu từ index 1.
Nó không tính các thay đổi động khi bạn dùng table.insert và sau đó table.remove ở giữa.
Nhưng bạn có thể chỉnh sửa như sau:

-- Bảng ví dụ
local myTable = {1, 2, 3, nil, 5, key1 = "value1", key2 = "value2"}
 
print(#myTable)  -- Output: 3
 
-- Metatable với phương thức __len
local meta = {
    __len = function(t)
        local count = 0
        for k, v in pairs(t) do
            if v ~= nil then
                count = count + 1
            end
        end
        return count
    end
}
 
-- Thiết lập metatable cho myTable
setmetatable(myTable, meta)
 
-- Bây giờ dùng toán tử # sẽ gọi __len
print(#myTable)  -- Output: 7

Hoặc bạn có thể trả về 0 vì lý do bảo mật.

6. So sánh bằng (_eq)

Bạn có thể định nghĩa hành vi tùy chỉnh khi so sánh hai bảng.

local table1 = {1, 2}
local table2 = {1, 2}
local meta = {
    __eq = function(a, b)
        return a[1] == b[1] and a[2] == b[2]
    end
}
setmetatable(table1, meta)
setmetatable(table2, meta)
 
print(table1 == table2)  -- Output: true

Bạn có thể ghi đè hành vi so sánh bảng bằng cách đặt metatable với _eq (cho == và ~=) và các phương thức khác để tùy chỉnh theo yêu cầu ứng dụng.

7. So sánh nhỏ hơn và nhỏ hơn hoặc bằng (_lt, _le)

Bạn có thể định nghĩa hành vi so sánh hai bảng sử dụng <.

local table1 = {1}
local table2 = {2}
local meta = {
    __lt = function(a, b)
        return a[1] < b[1]
    end
}
setmetatable(table1, meta)
setmetatable(table2, meta)
 
print(table1 < table2)

Lua không cung cấp sẵn cơ chế so sánh bảng theo nội dung bằng toán tử quan hệ. Bạn phải duyệt và so sánh thủ công nếu muốn so sánh theo nội dung.

8. Gọi hàm (_call)

Bạn có thể làm cho bảng hoạt động như một hàm.

local myTable = {}
local meta = {
    __call = function(table, arg)
        return "Called with argument: " .. arg
    end
}
setmetatable(myTable, meta)
 
print(myTable("Hello"))  -- Output: Called with argument: Hello

Khi bạn thiết lập _call, tham số đầu tiên luôn là chính bảng đó. Bạn có thể thêm các tham số khác sau.
Tương tự khi bạn dùng dấu : để gọi hàm trong bảng.

9. Chuyển bảng thành chuỗi (_tostring)

Bạn có thể định nghĩa cách bảng được chuyển sang chuỗi.

local myTable = {1, 2, 3}
local meta = {
    __tostring = function(t)
        return "This is my custom table!"
    end
}
setmetatable(myTable, meta)
 
print(myTable)  -- Output: This is my custom table!

Chú ý rằng print(myTable) không gọi myTable() mà là in trực tiếp bảng. Mặc định khi bạn print() một hàm, nó sẽ in như kiểu functionx123ZEW.

10. Thu gom rác bảng (_gc)

Bạn có thể định nghĩa hành vi khi bảng bị thu gom bởi garbage collector.

local myTable = {}
local meta = {
    __gc = function(t)
        print("Table is being garbage collected")
    end
}
setmetatable(myTable, meta)
 
myTable = nil
collectgarbage()  -- Output: Table is being garbage collected