学习Cocos2d-x Lua:解析Lua中的_index元方法以及_newindex元方法

2015年03月25日 11:45 1 点赞 0 评论 更新于 2017-05-09 14:30

这个系列主要聚焦于学习Cocos2d-x Lua,会总结Lua开发过程中涉及的知识点,以及阐述在开发过程中如何使用Cocos Code IDE。本文将详细介绍Lua中颇具趣味的内容——__index元方法以及与__index有点相似的__newindex元方法。

一、强大的__index元方法

我们先设想一下,当对一个table进行取值操作,但该table中根本没有这个值时会怎样呢?例如:

local t = {
name = "hehe"
}
print(t.money)

输出结果自然是:nil。这里的table t 仅定义了name字段,而我们尝试访问它的money字段,所以返回nil

但如果我们不希望出现这种情况,而是想在访问不存在的字段时进行一些自定义操作,该怎么办呢?Lua为我们提供了解决方案,那就是__index元方法。

在Lua里,使用加法操作时,会查找__add元方法;同理,当调用table中不存在的字段时,就会调用__index元方法。下面通过代码来具体说明:

local t = {
name = "hehe"
}
local mt = {
__index = function(table, key)
print("虽然你调用了我不存在的字段,不过没关系,我能探测出来:" .. key)
end
}
setmetatable(t, mt)
print(t.money)

在这段代码中,我们为table t 设置了一个自定义的元表mt,元表的__index元方法使用了一个自定义函数。运行结果如下:

[LUA-print] 虽然你调用了我不存在的字段,不过没关系,我能探测出来:money
[LUA-print] nil

当调用不存在的money字段时,就会调用table元表里的__index元方法,并且该方法会接收table本身和字段名两个参数。借助这个特性,我们可以在这个函数里实现很多自定义操作。

1. 继承的实现方法

虽然目前还未到详细讲解继承的时候,但我们可以先初步了解一下。当调用table中不存在的字段时,会调用table元表的__index元方法,这一点我们前面已经介绍过。不过,如果这个__index元方法是一个table,那么就会在这个table里查找字段并调用。下面通过代码来理解:

local t = {
name = "hehe"
}
local mt = {
__index = {
money = "900,0000"
}
}
setmetatable(t, mt)
print(t.money)

留意__index,我们给它赋值了一个table,该table中有一个money对象。当调用t的某个不存在的字段时,就会去查找__index里的table,如果找到该字段,就会调用它。输出结果如下:

[LUA-print] 900,0000

2. 试试继承

刚才的例子可能还无法让我们深刻体会到“继承”的概念,下面再看一个例子:

local smartMan = {
name = "none",
age = 25,
money = 9000000,
sayHello = function()
print("大家好,我是聪明的豪。")
end
}
local t1 = {}
local t2 = {}
local mt = {__index = smartMan}
setmetatable(t1, mt)
setmetatable(t2, mt)
print(t1.money)
t2.sayHello()

在这个例子中,我们定义了一个table smartMan,将其作为“基类”。然后新建了两个table t1t2,并把smartMan作为元表的__index元方法。这样,当调用t1t2money或者sayHello字段时,实际上就会找到smartMan的对应字段,这就很像继承的机制了。

二、也很强大的__newindex元方法

1. 查询与更新

上一节我们介绍了__index元方法,简单来说,__index元方法用于处理调用table中不存在的字段,需要注意的是,这里的“调用”仅指取值操作,而非赋值操作。

如果我们要对table中某个不存在的字段进行赋值,直接赋值是不会报错的。但如果我们想监控这个赋值操作,或者当有人试图对table中不存在的字段进行赋值时,我们想进行一些额外的处理,该怎么做呢?这时就需要用到__newindex元方法。大家要记住:__index用于查询,__newindex用于更新,初次接触时可能容易混淆。

2. 看看普通的赋值情况

我们先来看正常情况下的赋值操作,代码如下:

local smartMan = {
name = "none",
money = 9000000,
sayHello = function()
print("大家好,我是聪明的豪。")
end
}
local t1 = {}
local mt = {
__index = smartMan
}
setmetatable(t1, mt)
t1.sayHello = function()
print("en")
end
t1.sayHello()

这是上一节用过的模仿继承结构的例子。这里的mt作为t1的元表,__index被设置为smartMan。当我们调用t1中不存在的字段时,就会自动去smartMan中查找。例如调用t1.sayHello(),就能找到对应的函数。输出结果如下:

[LUA-print] en

我们调用t1sayHello字段,t1本身并不存在这个字段(虽然可以通过__index的方式找到smartMansayHello字段),但这并不影响给这个字段赋值,然后再调用t1.sayHello(),可以发现操作是成功的。这和我们平时对table做正常的赋值操作一样,不管table本身是否存在该字段。

3. 监控赋值

如果我们想监控table的赋值操作,对于不存在的字段不允许赋值,或者想制作一个只读的table,该如何实现呢?请参考下面的代码:

local smartMan = {
name = "none",
money = 9000000,
sayHello = function()
print("大家好,我是聪明的豪。")
end
}
local t1 = {}
local mt = {
__index = smartMan,
__newindex = function(table, key, value)
print(key .. "字段是不存在的,不要试图给它赋值!")
end
}
setmetatable(t1, mt)
t1.sayHello = function()
print("en")
end
t1.sayHello()

在这段代码中,我们留意元表mt,给它添加了一个__newindex元方法。运行代码,输出结果如下:

[LUA-print] sayHello字段是不存在的,不要试图给它赋值!
[LUA-print] 大家好,我是聪明的豪。

很明显,sayHello字段赋值失败。因为给sayHello字段赋值时,调用了__newindex元方法,该方法代替了原本的赋值操作。需要注意的是,t1中确实不存在sayHello字段,它只是因为有元表存在,且元表里的__index元方法的值是smartMan这个table,所以在t1找不到sayHello字段时,会去smartMan中寻找。但实际上,t1本身并没有sayHello字段。因此,当试图给t1sayHello字段赋值时,Lua判定该字段不存在,就会调用元表里的__newindex元方法。__newindex元方法被调用时会传入3个参数:table本身、字段名、想要赋予的值。

4. 隔山打牛,通过给一个table给另一个table的字段赋值

__index一样,__newindex元方法也可以赋予一个table值。这种情况比较有趣,下面通过代码来说明:

local smartMan = {
name = "none"
}
local other = {
name = "大家好,我是很无辜的table"
}
local t1 = {}
local mt = {
__index = smartMan,
__newindex = other
}
setmetatable(t1, mt)
print("other的名字,赋值前:" .. other.name)
t1.name = "小偷"
print("other的名字,赋值后:" .. other.name)
print("t1的名字:" .. t1.name)

在这段代码中,我们新加了一个table other,并把other作为__newindex的值。当给t1name字段赋值时,会出现一些意想不到的情况。输出结果如下:

[LUA-print] other的名字,赋值前:大家好,我是很无辜的table
[LUA-print] other的名字,赋值后:小偷
[LUA-print] t1的名字:none

可以看到,当给t1name字段赋值后,othername字段反而被赋值了,而t1name字段仍然没有变化(实际上t1name字段还是不存在的,它只是通过__index找到了smartManname字段)。也就是说,我们给t1name赋值时,实际上是给othername赋值了。

5. 总结规则

__newindex的规则如下:

  • 如果__newindex是一个函数,则在给table中不存在的字段赋值时,会调用这个函数。
  • 如果__newindex是一个table,则在给table中不存在的字段赋值时,会直接给__newindex对应的table赋值。

作者信息

boke

boke

共发布了 1025 篇文章