Account = {balance = 0} Account.__index = Account function Account:new(o) o = o or {} setmetatable(o, self) return o end a1 = Account:new() print(a1.balance)執行結果:
Programming in Lua (PIL) 2nd 13.4 指出,當取出 table 不存在的欄位時,interpreter 會進行兩步驟:
- 查詢 metatable(上述程式 Line 7)的欄位 __index (稱為 __index meta method) ,如果 __index 不為 nil(上面程式 Line 3),則由 __index 取得最終結果。__index 可以是下列兩種資料型態之一:
- table: 查詢此 table 的同名欄位(balance)
- function: 以 table(a1) 與不存在的 key(balance) 呼叫該 function
- 如果不存在 metatable 或 metatable 欄位 __index 為 nil,則回傳 nil。
Line 3 把 __index 指向自己(Account)初看有些混亂,既然 a1 (Line 11) 已經設定 metatable(Line7),那何不乾脆直接去 metatable 找就好?不過 Lua meta method 都依循這個規則,詳見 PIL Ch.13。
接下來這個例子可能會讓不少人抓頭老半天:
Account = {balance = 0} Account.__index = Account function Account:new(o) o = o or {} setmetatable(o, self) return o end a1 = Account:new() print(Account) print(Account.__index) print(a1.__index)輸出結果:
Line 15 輸出怎麼會跟 Line 13,14 一樣呢?
不過以前述的邏輯推論,沒有任何違和感:
- a1.__index 為 nil,於是查詢 metatable (Account) 欄位 __index
- metatable 欄位 __index 指向自己,照前面的解釋,如果 __index 是一個 table,則在該 table 查詢對應的欄位,那就是查詢 Account.__index,於是回傳 Account。
如不信,我們刻意把 a1.__index 設為 nil,看看結果如何:
Account = {balance = 0} Account.__index = Account function Account:new(o) o = o or {} setmetatable(o, self) return o end a1 = Account:new() print(Account) print(Account.__index) a1.__index = nil print(a1.balance)輸出結果:
最後,我們以一個例子來欣賞 Lua 的精妙之處:
Account = {balance = 0} Account.__index = Account function Account:new(o) o = o or {} setmetatable(o, self) return o end function Account:deposit(v) self.balance = self.balance + v end a1 = Account:new() a1:deposit(123) print(a1.balance) print(Account.balance)輸出結果:
各位可以看到 a1 有了自己的 balance 欄位,也沒有影響到他的 metatable - Account。
這是怎麼做到的?這裡就要對 Line 12 詳加解釋才行了:
- 因為 self.balance == nil,促使 interpreter 尋找 metatable 欄位 __index,於是找到 balance = 0。
- self.balance = 0 + v,此時寫回 self 本身(a1),創建新欄位 balance。
這裡要澄清的是當對 table 寫入不存在的欄位時,會喚起的是該 table 的 metatable 欄位 __newindex (meta method),這裡並沒有用到 __newindex,會提到這個的原因是避免有人誤以為讀寫 table 不存在的欄位時都會喚起 metatable __index。(對 __newindex 有興趣的朋友請自行參閱 PIL 13.4)
講的很清楚,很容易理解。
回覆刪除順便提一下前置知識:訪問一個table的元素時,若該元素不存在,則訪問該table的__index這個meta function,若連__index都不存在,就返回nil
文章最後提到的寫入不存在的元素時,訪問的則是__newindex這個meta function
謝謝作者展現了lua的精妙之處!