元表

使用元表可以自定义对象的默认行为、操作符。

什么是元表

一个table对象可以用另一个table对象(元表)来定义一些元方法(metamethods)。
用来定义元方法的表称为元表(metatable)。元表(metatable)允许我们改变表(table)的行为。

元表(metatable)中的函数称为元方法,通常用来重定义运算符。
例如对两个表(table)进行相加时,它会检查两个表是否有一个表有元表(metatable),
并且检查元表(metatable)是否有_add函数。如果找到则调用这个_add函数去计算结果。

保护元表

一个table对象指定了元表以后,默认创建的是保护元表、保护元表是禁止移除的(当然也就不能指定新的元表)
如果在元表中声明了 _float 属性,并赋值为真、则该元表不受保护并且可以随时被移除。

读取、设置元表

tab = {

@{ //使用@操作符设置元表

_get = function(k) begin

io.print(k)
return owner[[k]] //不调用元方法

end;

_float = true; //允许移除元表

}

}

tab@ = {}; //也可以这样设置元表
tab@ = {}; //这句会出错,因为元表没有指定_float属性时不能被移除


f = tab@._get; //读取元表


元属性/元方法列表

请参考:运算符、表达式 - 运算符重载


元表中的属性、方法列表:
元属性/元方法 说明
_weak 弱引用不会增加引用计数、不会阻止垃圾回收器删除对象。
赋值为 "kv" 表示弱引用键、值。
赋值为 "v" 表示弱引用值。
赋值为 "k" 表示弱引用键。
_type 自定义类型,如果值为"object",指明该对象为JSON兼容的对象, 如果值为"array",指明该对象为JSON的数组。
_readonly 如果显式指定此元属性为false,则该表可以重写属性名首字符为下划线的字段( 禁用读成员保护 )。

设置 _readonly 为任何非null值都会被强制转换为 false, 只有不指定此属性的值(即保持null值)才能启用只读成员保护(所有名字以下划线开头的属性禁止修改非null值)。

global对象无论元属性_readonly怎么设置都会被忽略,只读成员保护总是启用状态。
_defined 用于返回对象的预定义已排序键名,被用于table.eachName等函数。
_keys 可用于table.keys等函数动态获取对象的键名列表(例如动态生成键值对的外部JS对象可使用这个元方法返回成员名字列表)。
_startIndex 用于table.eachIndex等函数动态指定数组的开始下标。
_get = function(k,ownerCall) {

}
索引操作符[]

如果读取表中不存在的键会触发_get元方法并返回值。
_get不但可以是一个函数,也可以是一个table对象(找不到成员就到_get指定的table里找),如果是一个函数,调用参数k为键名,ownerCall如果为真则应当返回一个函数 - 这表示使用 对象.函数名() 的格式获取并调用成员函数。
_set = function(k,v) {

}
索引操作符[]

当你给表的一个缺少的键赋值时会触发_set元方法。参数k为键名,参数v为新值
_eq = function(b) {

}
相等运算、不等运算符调用此元方法并取反
比较的两个对象必须指向相同的元方法(即 a@._eq === b@._eq ),否则默认规则进行比较.
_le = function(b) {

}
小于等于、大于等于运算符
比较的两个对象必须指向相同的元方法(即 a@._le === b@._le )

当调用 a <= b 时, a为元方法的owner对象(左参数)
当调用 a >= b 时, b为元方法的owner对象(右参数作为左参数)
_lt = function(b) {

}
小于运算、大于运算符
比较的两个对象必须指向相同的元方法(即 a@._lt === b@._lt )

当调用 a < b 时, a为元方法的owner对象(左参数)
当调用 a > b 时, b为元方法的owner对象(右参数作为左参数)
_add = function(b) {

}
加运算符

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_sub = function(b) {

}
减运算符

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_mul = function(b) {

}
乘运算符

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_div = function(b) {

}
除运算符

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_lshift = function(b) {

}
左移运算符 <<

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_rshift = function(b) {

}
右移运算符 >>

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_mod = function(b) {

}
模运算符

调用元方法时,始终取左操作数作为元方法的owner参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_pow = function(b) {

}
幂运算符

调用元方法时,始终取左操作数作为元方法的 owner 参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_unm = function() {

}
取负运算符,owner 参数为当前操作数
_len = function() {

}
取长运算符(table,string,null 这三种类型不支持),owner 参数为当前操作数
_concat = function(b) {

}
连接运算符

调用元方法时,始终取左操作数作为元方法的 owner 参数.

无论左操作数或右操作参数定义了此元方法,都可以触发自定义的运算.

当左右操作数定义了不同的元方法,调用左操作数的元方法.
_call = function(...) {

}
函数调用,owner 参数为当前操作数,其他参数为调用函数的参数。

重载成员操作符

如果访问表中不存在的属性会调用 _get元方法,如果修改表中不存在的属性调用 _set元方法;

下面是一个示例,创建代理对象:

//创建一个代理,为另一个table对象创建一个替身以监控对这个对象的访问
function table.createProxy(tab) {
    var proxy = {};//创建一个代理表
    var real = tab;//在闭包中保存被代理的数据表tab

    proxy@ = {};//创建元表
    proxy@._get = function(k) begin
        io.print(k+"被读了")
        return real[k];
    end;
    proxy@._set = function (k,v) begin
        io.print(k+"被修改值为"+v)
        real[k]=v; //删除这句代码就创建了一个只读表
    end

    return proxy; //你要访问真正的表?先问过我吧,我是他的经纪人!!!
}

//下面是使用示例

tab = {x=12;y=15};
proxy = table.createProxy(tab);//创建一个代理表,以管理对tab的存取访问

io.open();
c = proxy.x; //显示 "x被读了"
proxy.y = 19; //显示 "y被修改值为19"
io.print(proxy.y); //显示 "y被读了" 然后显示19

范例

下面是一个示例,通过修改元表重定义dll对象的api函数。

User32 := raw.loadDll("User32.dll");

User32@.api2 = User32@.api
User32@.api = function(f,p,c){
   io.print(f,p,c)
   return owner.api2(f,p,c)
}

io.open();
::Kernel32 := ..raw.loadDll("Kernel32.dll");
::GetModuleHandle = Kernel32.api( "GetModuleHandleA", "pointer(string)")