Lua Academy · บทเรียน

__index และ __newindex

ดักจับการอ่านและเขียนฟิลด์ด้วยเมทาเมธอด __index และ __newindex

บทเรียน 2 จาก 412 ขั้นตอน

__index และ __newindex เป็นบทเรียน Lua Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Lua Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Lua Academy มีบทเรียนทั้งหมด 4 บทเรียน

ทบทวน __index

__index จะทำงานเมื่อ Lua พยายามอ่านคีย์ที่ไม่มีอยู่ในตาราง โดยอาจเป็นตารางสำหรับค้นหาในต้นแบบ หรือเป็นฟังก์ชันสำหรับการคำนวณแบบไดนามิก นี่คือเมธอดแปลงค่าที่ใช้บ่อยที่สุดใน Lua โดยรองรับการสืบทอด ค่าเริ่มต้น และการเริ่มต้นแบบประเมินเมื่อจำเป็น

local defaults = {timeout=30, retries=3}
local config = setmetatable({timeout=60}, {__index=defaults})

print(config.timeout)   -- 60 (own value)
print(config.retries)   -- 3  (from defaults)
print(config.missing)   -- nil (not in either)

__newindex ถูกเรียกใช้งาน

__newindex จะทำงานเมื่อ Lua พยายาม เขียนคีย์ที่ยังไม่มีอยู่ในตาราง หากคีย์นั้นมีอยู่แล้ว การกำหนดค่าจะเกิดขึ้นโดยตรงและไม่เรียกใช้ __newindex

local proxy = setmetatable({}, {
  __newindex = function(t, k, v)
    print("New key:", k, "=", v)
    rawset(t, k, v)  -- actually store it
  end
})

proxy.x = 10    -- New key: x = 10
proxy.x = 20    -- no trigger! x already exists
print(proxy.x)  -- 20

ตารางแบบอ่านอย่างเดียว

ใช้ __newindex เพื่อสร้างตารางแบบอ่านอย่างเดียว โดยดักจับการเขียนทั้งหมดและทำให้เกิดข้อผิดพลาด ใช้ร่วมกับ __index เพื่อจัดเตรียมค่าโดยไม่ต้องเก็บค่าเหล่านั้นไว้ในตารางจริง

local function readOnly(t)
  return setmetatable({}, {
    __index = t,
    __newindex = function(_, k, _)
      error("attempt to write to read-only table key: " .. tostring(k), 2)
    end
  })
end

local cfg = readOnly({host="localhost", port=8080})
print(cfg.host)    -- localhost
-- cfg.host = "x"  -- ERROR: attempt to write to read-only table

การติดตามการเปลี่ยนแปลง

ใช้ __newindex เพื่อติดตามการเปลี่ยนแปลงทั้งหมดของตาราง ซึ่งมีประโยชน์สำหรับการแก้จุดบกพร่อง การบันทึกการเปลี่ยนแปลง หรือการผูกข้อมูลแบบตอบสนอง โดยเก็บข้อมูลไว้ในตารางเบื้องหลังที่ซ่อนอยู่ และบันทึกการเปลี่ยนแปลงลงในบันทึก

local log = {}
local data = {}
local tracked = setmetatable({}, {
  __index = data,
  __newindex = function(_, k, v)
    log[#log+1] = {key=k, old=data[k], new=v}
    data[k] = v
  end
})

tracked.name = "Alice"
tracked.age = 30
tracked.name = "Bob"

for _, entry in ipairs(log) do
  print(entry.key, entry.old, "->", entry.new)
end

วัตถุพร็อกซี

พร็อกซีจะห่อหุ้มวัตถุอื่นเพื่อดักจับการอ่านและการเขียนทั้งหมด ใช้ตารางว่างเป็นพร็อกซี โดยให้ทั้ง __index และ __newindex ชี้ไปยังฟังก์ชัน ข้อมูลจริงจะอยู่ในตารางแยกต่างหาก

local function makeProxy(target)
  return setmetatable({}, {
    __index = function(_, k) return target[k] end,
    __newindex = function(_, k, v)
      print("Setting", k, "on target")
      target[k] = v
    end,
  })
end

local real = {x=10}
local p = makeProxy(real)
print(p.x)   -- 10 (reads from real)
p.y = 20     -- Setting y on target
print(real.y) -- 20

สายโซ่ __index (การสืบทอด)

การเชื่อมเมตาเทเบิลผ่าน __index จะสร้างการสืบทอดจากต้นแบบ เมื่อไม่พบคีย์ในวัตถุ Lua จะตรวจสอบคลาส จากนั้นตรวจสอบคลาสแม่ต่อไปเรื่อย ๆ แต่ละระดับต้องมีเมตาเทเบิลของตนเอง โดยให้ __index ชี้ไปยังระดับถัดไป

local Base  = {type = "base",  version = 1}
local Child = setmetatable({type = "child"}, {__index = Base})
local Obj   = setmetatable({}, {__index = Child})

print(Obj.type)     -- base? No: child (Child has it)
print(Obj.version)  -- 1 (from Base)
print(Obj.type)     -- child (Child overrides Base.type)

การเริ่มต้นแบบประเมินเมื่อจำเป็นด้วย __index

ใช้ __index ในรูปแบบฟังก์ชันเพื่อคำนวณและแคชค่าที่ใช้ทรัพยากรมากเมื่อมีการเข้าถึงครั้งแรก ในการอ่านครั้งแรก ให้คำนวณค่าแล้วเก็บลงในตารางโดยตรง เพื่อข้ามการเรียกใช้ __index ในครั้งต่อ ๆ ไป

local lazy = setmetatable({}, {
  __index = function(t, k)
    if k == "expensiveData" then
      print("Computing...")
      local result = {1,2,3,4,5}   -- simulate work
      rawset(t, k, result)
      return result
    end
  end
})

print(lazy.expensiveData)  -- Computing...  then table
print(lazy.expensiveData)  -- (no "Computing" this time)

__newindex สำหรับตรวจสอบสคีมา

ใช้ __newindex เพื่อบังคับใช้สคีมา โดยอนุญาตให้กำหนดค่าได้เฉพาะคีย์ที่กำหนดไว้ล่วงหน้า หรือค่าที่มีชนิดถูกต้อง วิธีนี้เพิ่มความปลอดภัยด้านชนิดข้อมูลให้กับตาราง Lua ได้ในระดับหนึ่ง

local schema = {name="string", age="number", active="boolean"}

local function schemaTable()
  local store = {}
  return setmetatable({}, {
    __index = store,
    __newindex = function(_, k, v)
      local expected = schema[k]
      if not expected then error("unknown key: "..k,2) end
      if type(v) ~= expected then
        error(k.." must be "..expected..", got "..type(v),2)
      end
      store[k] = v
    end
  })
end

local user = schemaTable()
user.name = "Alice"
user.age  = 30
-- user.age = "thirty"  -- ERROR

__index เทียบกับ rawget

ภายในฟังก์ชัน __index ให้ใช้ rawget เพื่ออ่านค่าจากตารางโดยไม่เรียกใช้ __index ซ้ำ ซึ่งอาจทำให้เกิดการเรียกซ้ำไม่สิ้นสุด ภายในเมธอดแปลงค่า ควรใช้ rawget/rawset เพื่อจัดการพื้นที่จัดเก็บจริงของตารางเสมอ

local counter = setmetatable({count=0}, {
  __index = function(t, k)
    -- Use rawget to avoid recursion
    local c = rawget(t, "count")
    rawset(t, "count", c + 1)
    return rawget(t, k)
  end
})

print(counter.count)    -- 0 (direct, no __index)
print(counter.missing)  -- nil
print(counter.count)    -- 1 (incremented)

การรวม __index และ __newindex

รูปแบบที่ทรงพลังที่สุดคือการใช้ตารางพร็อกซีว่างที่มีทั้ง __index และ __newindex โดยมีตารางข้อมูลที่ซ่อนอยู่เป็นแหล่งข้อมูลเบื้องหลัง วิธีนี้ช่วยควบคุมทั้งการอ่านและการเขียนได้อย่างเต็มที่ และรองรับความสามารถต่าง ๆ เช่น การตรวจจับการเปลี่ยนแปลง การโหลดเมื่อจำเป็น และการควบคุมการเข้าถึง

local function observable(init)
  local data = init or {}
  local listeners = {}
  local obj = setmetatable({}, {
    __index = data,
    __newindex = function(_, k, v)
      local old = data[k]
      data[k] = v
      for _, cb in ipairs(listeners) do cb(k,old,v) end
    end
  })
  obj._onchange = function(_, cb) listeners[#listeners+1]=cb end
  return obj
end

local obs = observable({score=0})
obs:_onchange(function(k,o,n) print(k,o,"->",n) end)
obs.score = 100  -- score  0 -> 100

ตรวจสอบด่วน

__newindex จะไม่ถูกเรียกใช้งานเมื่อใด

ทบทวน: __index และ __newindex

ประเด็นสำคัญ:

  • __index: ทำงานเมื่ออ่านคีย์ที่ไม่มีอยู่ อาจเป็นตารางหรือฟังก์ชัน
  • __newindex: ทำงานเฉพาะเมื่อเขียนคีย์ใหม่
  • ใช้ rawget/rawset ภายในเมธอดแปลงค่าเพื่อหลีกเลี่ยงการเรียกซ้ำ
  • รูปแบบการใช้งาน: ตารางแบบอ่านอย่างเดียว การติดตามการเปลี่ยนแปลง การเริ่มต้นแบบประเมินเมื่อจำเป็น และการตรวจสอบสคีมา
  • พร็อกซีว่าง + ตารางเบื้องหลัง = การดักจับการอ่านและการเขียนอย่างเต็มรูปแบบ
เริ่มต้นได้ฟรี

เรียนรู้ Lua ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
40
บทเรียน
159

คำถามที่พบบ่อย

บทเรียน “__index และ __newindex” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “__index และ __newindex” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Lua Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Lua Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “__index และ __newindex”

ดักจับการอ่านและเขียนฟิลด์ด้วยเมทาเมธอด __index และ __newindex คุณปฏิบัติ Lua Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Lua Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Lua Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “__index และ __newindex” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Lua Academy นี้ได้ไหม

ได้ บทเรียน Lua Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. รู้จักเมทาเทเบิล
  2. __index และ __newindex
  3. เมทาเมธอดทางเลขคณิต
  4. __tostring และ __len
← กลับไปที่ Lua Academy