Lua防止创建未预期的全局变量详解

佐手、 提交于 2020-01-20 03:16:58

本文介绍cocos Lua项目的禁用创建默认环境下全局变量的做法,代码取自src\cocos\framework\init.lua,先直接上代码

-- export global variable
local __g = _G
cc.exports = {}
setmetatable(cc.exports, {
    __newindex = function(_, name, value)--若需要赋值全局变量比如a=2,需要写成cc.exports.a=2
        rawset(__g, name, value)         --通过__newindex元方法将对cc.exports的赋值转变为对_G的赋值,因此并不对cc.exports赋值
    end,                                 --因此访问时,直接访问_G即可:print(a)  --打印2

    __index = function(_, name)
        return rawget(__g, name)--通过__index元方法将对cc.exports的访问转变为对_G的访问
    end
})

-- disable create unexpected global variable
function cc.disable_global()
    setmetatable(__g, {
        __newindex = function(_, name, value)--全局环境一旦赋值后会调用这里设置的__newindex元方法,提示错误,并不执行赋值
            error(string.format("USE \" cc.exports.%s = value \" INSTEAD OF SET GLOBAL VARIABLE", name), 0)
        end
    })
end

if CC_DISABLE_GLOBAL then--CC_DISABLE_GLOBAL控制是否启用防止创建未预期的全局变量,CC_DISABLE_GLOBAL在config.lua里
    cc.disable_global()
end

如果读者已对元表,__index,__newindex,rawset(),rawget()等概念和方法熟悉的话,仅阅读代码和注释即可,无需阅读下文

该段代码涉及到以下知识点:

1.元表

Lua 中的每个值都可以有一个 元表。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。 例如,当你对非数字值做加操作时, Lua 会检查该值的元表中的 "__add" 域下的函数。 如果能找到,Lua 则调用这个函数来完成加这个操作。

元表中的键对应着不同的 事件 名; 键关联的那些值被称为 元方法。 在上面那个例子中引用的事件为 "add" , 完成加操作的那个函数就是元方法。

你可以用 getmetatable 函数 来获取任何值的元表,使用 setmetatable 来替换一张表的元表。

元表决定了一个对象在数学运算、位运算、比较、连接、 取长度、调用、索引时的行为。 元表还可以定义一个函数,当表对象或用户数据对象在垃圾回收时调用它。应用举例:

    local mt = {}
    mt.__add = function(first,second)
        print("calling __add")
        return first.value + second.value
    end
    local a = {value = 5}
    local b = {value = 12}
    setmetatable(a, mt)
    print(a+b)

2.__index

 索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。 此时,会读出 table 相应的元方法。尽管名字取成这样, 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table 和 key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。 (这个索引过程是走常规的流程,而不是直接索引, 所以这次索引有可能引发另一次元方法。)

Lua查找元素的规则如下: 
1.在表中查找,找到则返回,找不到则继续 
2.判断是否有元表,没有返回nil,有则继续 
3.判断元表有无__index方法,如果该方法为nil,则返回nil;如果是一个表,则重复①②③;如果是一个函数,则返回函数的返回值(table和key会作为参数传递进去)。

应用举例:

    --__index为表
    local player1 = {sex = "male"}
    local tName = {name = "ivey"}
    local mtName = {__index = tName}
    local mtLevel = {__index = {level = 99}}
    setmetatable(tName, mtLevel)
    setmetatable(player1, mtName)
    print(player1.sex)--player1中找到
    print(player1.name)--mtName中找到
    print(player1.level)--mtLevel中找到
    --__index为函数
    local player2 = {}
    setmetatable(player2,{__index = function(table,key)
        print(table,key .. " is not found")
        return table,key
    end})
    print(player2.name)

3.__newindex

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

    --__newindex为表
    local player1 = {sex = "male"}
    local tName = {name = "ivey"}
    local mtName = {__newindex = tName}
    setmetatable(player1, mtName)
    player1.sex = "female"
    player1.name = "Alice"
    print(player1.sex)--player1中找到
    print(player1.name, tName.name)--在tName中找到
    --__newindex为函数
    local player2 = {}
    setmetatable(player2,{__newindex = function(table,key,value)
        print(table, key, 0)
    end})
    player2.name = 15--该赋值不会执行
    print(player2.name)

4.rawget(table,key)

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

5.rawset(table,key,value)

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

    --rawget
    local player1 = {sex = "male"}
    setmetatable(player1, {__index = {age = 20}})
    print(player1.age, rawget(player1,age))--rawget可跳过从元表取值步骤
    --rawset
    local player2 = {}
    setmetatable(player2,{__newindex = function(table,key,value)
        rawset(table,key,5)
    end})
    player2.age = 15--该赋值不会执行
    print(player2.age)

实战遇到的面试题:

1.如何禁用全局变量

2.元表做什么用的

3.__index和__newindex的区别

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!