Menu
CoddyTech

Metamethod __newindex

Thuộc phần Logic và luồng chương trình trong hành trình Lua của Coddy. Bài học 19 trên 54.

Bạn đã học cách __index kiểm soát điều gì xảy ra khi bạn thử đọc một khóa không tồn tại trong bảng. Giờ bạn sẽ tìm hiểu phần tương ứng của nó: __newindex, kiểm soát điều gì xảy ra khi bạn gán giá trị cho một khóa mà bảng chưa có.

Phương thức meta __newindex được kích hoạt mỗi khi bạn thử gán giá trị cho một khóa chưa tồn tại trong bảng. Nhờ vậy, bạn có thể chặn và kiểm soát các thay đổi trước khi chúng xảy ra.

Đây là một ví dụ đơn giản ngăn không cho thêm khóa mới vào bảng:

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

setmetatable(readOnly, meta)

readOnly.name = "Alice"  -- Đầu ra: Cannot add key: name
print(readOnly.name)     -- Đầu ra: nil

Khi bạn thử gán readOnly.name = "Alice", Lua thấy rằng name không tồn tại trong bảng. Thay vì thêm thuộc tính này, Lua gọi hàm __newindex với ba đối số: chính bảng đó, khóa đang được gán và giá trị được gán.

Trong ví dụ này, hàm (function) chỉ in ra một thông báo và không thực sự lưu trữ gì cả.

Bạn cũng có thể dùng __newindex để xác thực dữ liệu trước khi cho phép đưa dữ liệu vào bảng:

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"  -- Đầu ra: Health must be a number!
player.health = 100     -- Hợp lệ, được lưu bằng rawset
player.health = "high"  -- Không có thông báo: health đã tồn tại

Hãy chú ý cách dùng rawset() trong ví dụ thứ hai. Hàm này bỏ qua metatable và đặt trực tiếp giá trị vào bảng.

Nếu không có nó, việc đặt giá trị sẽ kích hoạt lại __newindex, tạo ra một vòng lặp vô hạn.

Vì __newindex chỉ chạy với những khóa chưa có trong bảng, nên bước kiểm tra này bảo vệ lần gán đầu tiên cho mỗi khóa. Sau khi rawset đã lưu health, các lần gán sau sẽ thay đổi trực tiếp giá trị đó. Để kiểm tra mọi lần gán, hãy giữ cho chính bảng luôn trống và lưu các giá trị trong một bảng thứ hai, rồi đọc chúng thông qua __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"  -- Đầu ra: Health must be a number!
print(player.health)    -- Đầu ra: 100

player không có khóa riêng, nên mọi phép gán đều đi qua __newindex, còn mọi lần đọc đều đi qua __index để đến data.

challenge icon

Thử thách

Dễ

Viết một hàm createValidatedTable nhận key và value, rồi trả về một bảng chỉ chấp nhận giá trị chuỗi cho các khóa mới.

Tạo một bảng rỗng và gắn metatable có __newindex để kiểm tra kiểu dữ liệu của giá trị. Nếu có người cố thêm một giá trị không phải chuỗi, hãy dùng rawset() để lưu chuỗi "invalid" thay thế. Với giá trị chuỗi, hãy dùng rawset() để lưu như bình thường.

Lô-gic:

  • Tạo một bảng rỗng
  • Tạo một metatable có __newindex nhận bảng, khóa và giá trị
  • Bên trong __newindex, kiểm tra xem giá trị có phải là chuỗi hay không bằng type(value) == "string"
  • Nếu đó là chuỗi, dùng rawset(t, key, value) để lưu giá trị
  • Nếu đó không phải là chuỗi, dùng rawset(t, key, "invalid") để lưu "invalid" thay thế
  • Gắn metatable vào bảng bằng setmetatable()
  • Dùng rawset() để thêm cặp khóa-giá trị ban đầu vào bảng, bỏ qua metatable
  • Trả về bảng

Tham số:

  • key (chuỗi): Khóa ban đầu cần thêm vào bảng
  • value (chuỗi): Giá trị ban đầu cần thêm vào bảng

Giá trị trả về: Một bảng có cơ chế kiểm tra, chỉ chấp nhận giá trị chuỗi và chứa cặp khóa-giá trị ban đầu (bảng)

Tự làm thử

function createValidatedTable(key, value)
    -- Viết code tại đây
end
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Logic và luồng chương trình

Tự luyện tập: Trình biên dịch Lua online