0Pricing
Lua Academy · درس

‏__index و__newindex

اعترض عمليات قراءة الحقول وكتابتها باستخدام طريقتي metamethod ‏__index و__newindex

‏__index و__newindex درس مجاني في Lua Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 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

استخدم rawget داخل دالة __index للقراءة من الجدول من دون تفعيل __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 داخل دوال الميتا لتجنب الاستدعاء التكراري
  • الأنماط: الجداول للقراءة فقط، وتتبع التغييرات، والتهيئة الكسولة، والتحقق من المخطط
  • وكيل فارغ + جدول داعم = اعتراض كامل لعمليات القراءة والكتابة

الأسئلة الشائعة

هل درس «‏__index و__newindex» مجاني؟

نعم — نص درس «‏__index و__newindex» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Lua Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Lua Academy 4 دروس في المجموع.

ماذا ستتعلم في «‏__index و__newindex»؟

اعترض عمليات قراءة الحقول وكتابتها باستخدام طريقتي metamethod ‏__index و__newindex تتمرن على Lua Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Lua Academy؟

لا تُشترط خبرة سابقة. Lua Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «‏__index و__newindex»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Lua Academy هذا؟

نعم. كل درس في Lua Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. مقدمة إلى جداول البيانات الوصفية
  2. ‏__index و__newindex
  3. الطرق الوصفية الحسابية
  4. ‏__tostring و__len
← العودة إلى Lua Academy