在原型 "constructor" 内部或外部定义 __index

Define __index inside prototype "constructor" or outside

我正在 https://www.lua.org/pil/16.html 上阅读“Lua 编程”中的“面向对象编程”一章。

在那个例子中,他们创建了这个“构造函数”:

Account = {balance = 0}
function Account:new (o)
  o = o or {}   -- create object if user does not provide one
  setmetatable(o, self)
  self.__index = self
  return o
end

我在 Linux 上用一个简单的 time 在一个脚本中做了一些“sintetic 基准测试”,该脚本有 1000 万次这样的操作。在 table 初始化之外定义 Account.__index = Account 快 200 毫秒。

我的问题是,如果我们可以在外部定义并执行一次,为什么要在这个函数内部设置每次调用该函数都会执行的self.__index?也许是遗产?

已编辑:

谢谢luther的解答,我这里就举个例子给有这个疑问的大家:

local a = {}
a.__index = a
function a:foo()
    return 'foo'
end

function a:new(o)
    print(self)
    o = o or {}
    setmetatable(o, self)
    -- self.__index = self
    return o
end

local b = a:new()
-- b.__index = b
function b:bar()
    return 'bar'
end

local z = a:new()
print(z:foo()) -- this will work

local y = b:new()
print(y:foo()) -- attempt to call method 'foo' (a nil value)
print(y:bar()) -- attempt to call method 'bar' (a nil value)

当然 y 会有元 table 和 b 一样 table,但是 b 没有 __index条目,此条目仅在b的元table中。 如果您仍想避免在“构造函数”中声明 __index,则需要在每个派生原型或“子类”中指定它。

PiL 的作者似乎试图通过让 new 方法像处理所有 child 一样处理根 object 来简化事情 objects。这可能会让初学者感到困惑,因为不能立即清楚 self.__index = self 通常是多余的。

此外,这样做实际上比在每个 object 上添加一个 __index 更快。请记住,在原型系统中,每个 object 都可能是其他 object 的原型。在我的机器上,通过 1e8 次试验,PiL 方式需要 14 秒,而将 __index 添加到所有 object 需要 23 秒。新密钥意味着 table 必须增长,因此它比分配给已存在的密钥慢。

令人困惑的是,这个 PiL 部分的标题是“类”,但在第一段中,他说他正在模拟 prototype-based 种语言,其中“object 没有 class是的。”这进一步搞砸了 reader 的期望。本节实现 self-replicating object,而不是 class.

下面是我的实现方式,虽然不那么令人困惑,但速度较慢:

Account = {balance = 0}
Account.__index = Account

function Account:new (o)
  o = o or {}   -- create object if user does not provide one
  setmetatable(o, self)
  o.__index = o
  return o
end