美文网首页
Lua index 和 newindex 元方法

Lua index 和 newindex 元方法

作者: eddy_wiki | 来源:发表于2017-06-26 23:49 被阅读0次

    上一篇文章简单介绍了 Lua 中的元表和元方法,那么在这里就接着重点讲解一下 Lua 中的 index 和 newindex 元方法。

    • **"index": **索引 table[key]。当 table 不是表或是表 table 中不存在key 这个键时,这个事件被触发。此时,会读出 table 相应的元方法。尽管名字取成这样,这个事件的元方法其实可以是一个函数也可以是一张表。如果它是一个函数,则以 tablekey 作为参数调用它。如果它是一张表,最终的结果就是以 key 取索引这张表的结果。(这个索引过程是走常规的流程,而不是直接索引,所以这次索引有可能引发另一次元方法。)
    • **"newindex": **索引赋值 table[key] = value 。和索引事件类似,它发生在table 不是表或是表 table 中不存在key 这个键的时候。此时,会读出 table 相应的元方法。同索引过程那样,这个事件的元方法即可以是函数,也可以是一张表。如果是一个函数,则以 tablekey、以及 value 为参数传入。如果是一张表,Lua 对这张表做索引赋值操作。(这个索引过程是走常规的流程,而不是直接索引赋值,所以这次索引赋值有可能引发另一次元方法。)一旦有了 "newindex" 元方法,Lua 就不再做最初的赋值操作。(如果有必要,在元方法内部可以调用 rawset来做赋值。)

    上面对 index 和 newindex 元方法的概念已经说得很清楚,在这就不再重复了。

    rawget 和 rewset 方法

    rawget (table, index) 在不触发任何元方法的情况下获取 table[index] 的值。table 必须是一张表;index 可以是任何值。

    rawset (table, index, value) 在不触发任何元方法的情况下将 table[index] 设为 valuetable 必须是一张表,index 可以是 nil 与 NaN 之外的任何值。value 可以是任何 Lua 值。这个函数返回 table

    index 元方法

    当访问一个 table 中不存在的字段时,会去查找一个叫 index 的元方法。如果没有这个元方法,那么访问结果就为nil,否则,就有这个元方法来提供最终结果。

    Window = {}
    Window.prototype = {x = 0, y = 0, width = 100, height = 100}
    Window.mt = {}
    
    function Window.new(o)
        setmetatable(o, Window.mt)
        return o
    end
    
    Window.mt.__index = function (table, key)
        print("call __index function", table, key)
        return Window.prototype[key]
    end
    
    w = Window.new({x = 10, y = 20})
    
    print("w", w)
    print("w.x =", w.x)
    print("w.y =", w.y)
    
    print("----------\n")
    print("rawget w.x =", rawget(w, "width"))
    print("w.width =", w.width)
    print("w.height =", w.height)
    
    print("----------\n")
    print("set w.width = 555")
    w.width = 555
    print("w.width =", w.width)
    

    执行上面代码会输出:

    w   table: 0x7fc932c08b70
    w.x =   10
    w.y =   20
    ----------
    
    rawget w.x =    nil
    call __index function   table: 0x7fc932c08b70   width
    w.width =   100
    call __index function   table: 0x7fc932c08b70   height
    w.height =  100
    ----------
    
    set w.width = 555
    w.width =   555
    

    index 元方法不必一定是一个函数,它还可以是一个 table。当他是一个 table 时,Lua 就以相同的方式来重新访问这个table。

    Window = {}
    Window.prototype = {x = 0, y = 0, width = 100, height = 100}
    Window.mt = {}
    
    function Window.new(o)
        setmetatable(o, Window.mt)
        return o
    end
    
    Window.mt.__index = Window.prototype
    
    w = Window.new({x = 10, y = 20})
    
    print("w.x =", w.x)
    print("w.y =", w.y)
    print("w.width =", w.width)
    print("w.height =", w.height)
    
    print("----------\n")
    print("set w.height = 888")
    w.height = 888
    print("w.height =", w.height)
    print("getmetatable(w).__index.height =", getmetatable(w).__index.height)
    

    执行上面代码会输出:

    w.x =   10
    w.y =   20
    w.width =   100
    w.height =  100
    ----------
    
    set w.height = 888
    w.height =  888
    getmetatable(w).__index.height =    100
    

    总结:一般我们会把一个 table 作为 index 元方法,只有在特殊需求时才会把一个函数作为 index 元方法。将一个 table 作为 index 元方法是一种快捷的、实现单一继承的方式。虽然用函数作为 index 来实现相同功能的开销很大,但函数更加灵活。可以通过函数来实现多重继承、缓存及其他功能。如果我们想在不触发 index 元方法的情况下访问一个 table,可以使用 rawget 方法。

    newindex 元方法

    当对一个 table 中不存在的索引赋值时,解释器就会查找 newindex 元方法。如果有这个元方法,解释器就调用它,而不是执行赋值。如果这个元方法是一个 table,解释器就在此 table 中执行赋值,而不是对原来的 table。调用 rawset(t, k, v) 就可以不涉及任何元方法而直接设置 table t 中与 key k 相关联的 value v。

    t = {a = 100}
    mt = {}
    
    mt.__newindex = function (t, key, value)
        print("call __newindex function", t, key, value)
    end
    setmetatable(t, mt)
    
    print("set t.a = 666")
    t.a = 666
    print("set t.b = 888")
    t.b = 888
    
    print("----------\n")
    print([=[rawset(t, "c", 999)]=])
    rawset(t, "c", 999)
    

    执行上面代码会输出:

    set t.a = 666
    set t.b = 888
    call __newindex function    table: 0x7f8842500000   b   888
    ----------
    
    rawset(t, "c", 999)
    

    ipairs 和 pairs

    t = {1, 2, a = "a"}
    mt = {}
    -- mt.__index = function (t, key)
    --  print("call __index function", t, key)
    -- end
    
    mt.__index = {111, 222, 333, 444, a = "aaa", b = "bbb"}
    
    setmetatable(t, mt)
    
    -- 从下列代码的打印可以看出,ipairs 会触发 __index 元方法
    print("--- ipairs --")
    for i,v in ipairs(t) do
        print(i,v)
    end
    
    print("")
    
    -- 从下列代码的打印可以看出,pairs 不会触发 __index 元方法
    print("--- pairs --")
    for k,v in pairs(t) do
        print(k,v)
    end
    

    执行上面代码会输出:

    --- ipairs --
    1   1
    2   2
    3   333
    4   444
    
    --- pairs --
    1   1
    2   2
    a   a
    

    从以上输出可以看出:ipairs 会触发 index 元方法;而 pairs 不会触发 index 元方法。

    简单应用实例

    具有默认值的 table

    local key = {} -- 唯一的 key
    local mt = {__index = function (t) return t[key] end}
    function setDefault(t, d)
        t[key] = d
        setmetatable(t, mt)
    end
    
    t = {x = 10, y = 10}
    print(t.x, t.z) --> 10 nil
    
    setDefault(t, 100)
    print(t.x, t.z) --> 10 100
    

    跟踪 table 的访问

    index 和 newindex 都是在 table 中没有所需访问的 index 时才发挥作用的。因此,只有将一个 table 保持为空,才有可能捕捉到所有对它的访问。为了监视一个 table的所有访问,就应该为真正的 table 创建一个代理。这个代理就是一个空的 table,其中index 和 newindex 元方法可用于跟踪所有的访问,并将访问重定向到原来的 table 上。

    如果想要同时监视几个 table,无须为每个 table 创建不同的元表。相反,只要以某种形式将每个代理与其原 table 关联起来,并且所有代理都共享一个公共的元表。

    local index = {}
    local mt = {
        __index = function (t, k)
            print("*access to element " .. tostring(k))
            return t[index][k]
        end,
    
        __newindex = function (t, k, v)
            print("*update of element " .. tostring(k) .. " to " .. tostring(v))
            t[index][k] = v
        end
    }
    
    function track(t)
        local proxy = {}
        proxy[index] = t
        setmetatable(proxy, mt)
        return proxy
    end
    
    t = {}
    t = track(t)
    
    t[1] = "hello"
    print(t[1])
    
    t["k"] = "v"
    print(t["k"])
    

    执行以上代码会输出:

    *update of element 1 to hello
    *access to element 1
    hello
    *update of element k to v
    *access to element k
    v
    

    只读 table

    function readOnly(t)
        local proxy = {}
        local mt = {
            __index = t,
            __newindex = function (t, k, v)
                error("attempt to update a read-only table", 2)
            end
        }
        setmetatable(proxy, mt)
        return proxy
    end
    
    days = readOnly{"Sunday", "Monday", "Tuesday"}
    print(days[1]) --> Sunday
    days[2] = "Noday" --> error: attempt to update a read-only table
    

    总结

    这里通过一些代码和实例讲解了 Lua 中的 index 和 newindex 元方法,但其实 index 元方法最常见的用途是用来实现面向对象编程中的类、继承和多重继承。后面会有单独的文章介绍怎么 Lua 中实现面向对象编程。


    本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/lua-index-metafunc.html

    相关文章

      网友评论

          本文标题:Lua index 和 newindex 元方法

          本文链接:https://www.haomeiwen.com/subject/murscxtx.html