这个系列主要学习Cocos2d-x Lua,总结Lua开发过程中所涉及的知识点,以及在开发过程中如何使用Cocos Code IDE。

本文介绍Lua中比较有意思的内容——_index元方法以及与_index有点相似的_newindex元方法。


一、强大的_index元方法

我们来想象一下,如果对一个table进行取值操作,但是table根本就没有这个值呢?比如:

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

输出结果当然是:nil

t只用于name这个字段,而我们却访问了它的money字段,自然是返回nil了。


但是,如果我们不希望这样呢?我们希望在访问不存在的字段时,进行一些自定义的操作呢?没问题,Lua满足了我们,那就是_index元方法


在使用加法操作时,会查找_add元方法,那么,在调用table不存在的字段时,会调用_index元方法,这是一样的规则。

 

我们来看看代码:

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

我们给table设置了一个自定义的元表,元表的_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,t1和t2,将smartMan作为元表的__index元方法。


于是,当调用t1、t2的money或者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


我们调用t1的sayHello字段,t1并不存在这个字段(虽然可以通过_index的方式来找到smartMan的sayHello字段)。但这不影响,给这个字段赋值,然后再调用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字段的,不知道大家能绕明白不?因此,当试图给t1的sayHello字段赋值时,Lua判定sayHello字段是不存在的,所以会去调用元表里的_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);

这次的代码和刚刚差不多,但是我们新加了一个other的table,然后把other作为_newindex的值。于是,当给t1的name字段赋值时,就会发生一些奇怪的事情…

先来看看输出结果:

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

[LUA-print] other的名字,赋值后:小偷

[LUA-print] t1的名字:none

当给t1的name字段赋值后,other的name字段反而被赋值了,而t1的name字段仍然没有发生变化。(实际上t1的name字段还是不存在的,它只是通过__index找到了smartMan的name字段,这个就不唠叨了。)

于是,我们给t1的name赋值的时候,实际上是给other的name赋值了。好吧,可怜的other。

 

5. 总结规则

这就是_newindex的规则:

a.如果_newindex是一个函数,则在给table不存在的字段赋值时,会调用这个函数。

b.如果_newindex是一个table,则在给table不存在的字段赋值时,会直接给_newindex的table赋值。