Lua类示例中为何构造函数要在self上设置__index?
嘿,我来帮你把这几种Lua里模拟类的构造写法掰扯清楚~
首先拆解第一种经典写法:
Account = {balance = 0} function Account:new (o) o = o or {} setmetatable(o, self) self.__index = self return o end
你觉得反复给Account设置__index奇怪是正常的,但其实这里的重复操作是幂等的——第一次调用new时,Account.__index还没赋值,这一步是必要的;之后再调用,哪怕重复设置成同一个值,也不会有任何问题,只是看起来有点冗余而已。
这种写法的核心优势是方便实现继承:比如你之后想写一个子类SavingsAccount,只需要这么写:
SavingsAccount = Account:new() function SavingsAccount:add_interest() self.balance = self.balance * 1.03 end
当调用SavingsAccount:new()时,函数里的self就是SavingsAccount,这时候会把实例的元表设为SavingsAccount,同时给SavingsAccount设置__index = self,这样子类的实例既能访问自身的方法(比如add_interest),又能通过元表链找到父类Account的属性和方法,完美实现原型继承。
再看第二种写法:
function Account:new (o) o = o or {} setmetatable(o, {__index = self}) return o end
正如@Egor指出的,这种写法最大的问题是每次调用new都会创建一个全新的元表对象。虽然功能上能让实例访问Account的内容,但如果创建几百上千个实例,就会生成几百个一模一样的元表,完全是内存浪费——毕竟所有实例的元表逻辑都是相同的,完全没必要重复创建。
最后看你提出的第三种优化写法:
Account = {balance = 0} Account_meta = {__index = Account} function Account:new (o) o = o or {} setmetatable(o, Account_meta) return o end
这种写法完全可行,而且非常高效!它只创建了一个共享的元表Account_meta,所有Account的实例都复用这个元表,既避免了第一种写法的冗余赋值,又解决了第二种写法的内存浪费问题。
那为什么第一种经典写法还是被广泛使用?主要是继承场景下的便利性:如果用第三种写法,子类需要额外处理元表的设置,不能直接复用父类的new方法,得手动给子类创建专属的元表,步骤会多一点。但如果你的场景不需要继承,第三种写法其实是更清爽的选择。
总结一下:
- 第二种写法不推荐,内存开销大;
- 第三种写法适合简单无继承的场景,高效简洁;
- 第一种写法是Lua原型继承的经典实现,冗余但方便扩展子类,适合需要继承的复杂场景。
内容的提问来源于stack exchange,提问作者Alan

