阅读 192

_G和_ENV

_G

  Lua中的全局变量是使用普通表实现的。对不是局部变量的任何访问都将重定向到该表。局部变量始终具有优先访问权。因此,如果同时存在一个全局变量和一个具有相同名称的局部变量,访问该变量时则始终会得到局部变量的值。

  使用_G可以打破这种状况:如果需要全局变量,可以显示写为_G.name而不是name。这是因为_G不是局部变量(它是Lua保留的),通过这种表索引语法来显式获取全局变量的值,从而消除局部和全局变量名的歧义。在较新的Lua版本(5.2+)中,您也可以使用_ENV.name作为替代,但是以前那些版本没有_ENV的存在只可以使用_G。

  上面列出的所有仅适用于Lua代码,不适用于C代码。在C代码中,不可以命名局部变量,只有堆栈索引也就是全是全局变量,而全局变量名不可以重复,因此访问一个变量不会有歧义。      而且,在c代码中如果希望将全局表的引用传递给某些函数,则可以使用lua_pushglobaltable(使用注册表而不是_G)。所以用C实现的lua标准库模块也不使用_G全局变量。   

_G 和__ENV的关系

lua语言把所有的代码都当作匿名函数处理,例如

local z = 10
x = y + 10
复制代码

实际上会被编译为

local _ENV = smoe value (也就是预定义上值,假设该值存在)
return function (...)
    local z = 10
    _ENV.x = _ENV.y + z
end
复制代码
  • 所以可以说lua是在一个名为_ENV的预定义上值存在的情况下编译所有代码段的,所以一个变量要么被绑定到了一个名称的局部变量,要么是_ENV中的一个字段。

_ENV不是全局环境,它指向的是当前环境,而Lua又确保每个chunk(可以是一句代码,函数体,do ...end之间的内容等)都以_ENV开头,所以每个chunk中都可以拥有自己独立的环境。在每个环境中对非局部a的所有访问都转换为_ENV.a。这种方式更安全,并且更加灵活,因为可以通过创建局部_ENV变量来为单个代码块的创建单独的环境,从而降低块与块之间的耦合度。

local _ENV = {print = print,a = 13} --- 称之为env
function func1()
    local _ENV = {print = print,a = 15}--- 称之为env1
    print(a)
end

function func2()
    local _ENV = {a = 14,print = print}--- 称之为env2
    print(a)
    print(_ENV.func1)
end

function func3()
    print(a)
end

func1() -- 15
_ENV.func1() -- 15
func2()
 -- 14
 -- nil
_ENV.func2()
 -- 14
 -- nil
func3() -- 13
print(math) --nil
复制代码
  • 我们首先在最上层创建了一个_ENV(env),所以其下面的代码块中若没有定义新的_ENV都将使用此_ENV(env)作为上值,也就意味着访问非局部变量(此处指不带local的变量)时都将从此_ENV(env)中查找.
  • 所以func1,func2,func3都定义在了_ENV(env)中。所以func1()和_ENV.func1()调用输出结果一样。
  • 然后又在func1中定义了新的_ENV(env1),只是在访问非局部变量时(意思同上),都将从此_ENV(env1)中查找,所以func1输出a的值为15,func为13;又因为_ENV(env1)中没有func1函数所以为nil

  lua语言在编译运行时首先会创建一个默认环境(也称为“全局环境”,对应这上面的_G表)并将其存储在注册表中。除非您将自定义环境(也就是自定义的_ENV)传递给load或loadfile,否则此默认环境将用作所有块的_ENV。lua_pushglobaltable还直接从注册表中检索此全局环境,因此所有C模块都会自动使用它来访问全局变量。并且,如果标准的C模块已加载,则此默认的“全局环境”具有一个名为_G的表字段,该表字段引用回全局环境。

简单的说就是首先lua会有一个_G表,若没有人为的设置,则这个_G表直接就被用作的_ENV(而且所有的块都将以此_ENV开头),然后_ENV的里存储着一个键值对_G = _G(全局环境表)。任何对该环境的更改也将直接作用到_G上,因为这两个初始时引用的同一个表,类似下面

print(_G) -- table: 0000021DC4BE6700
print(_ENV._G) -- table: 0000021DC4BE6700
复制代码

当然我们可以创建自己的环境,并继承全局环境,但不改变全局环境

b = 25
_G = setmetatable(_G,{__newindex = function() print("can change this table") end})
a = 13 
local _ENV = setmetatable({},{__index = _G})
c = 14
print(a,b,c)
print(_G.a,_G.b,_G.c)
复制代码

输出

-- can change this table
-- nil	25	14
-- nil	25	nil
复制代码

以前的文章中提到过可以通过load函数加载编译字符串中的代码段 load(chunk:string,chunkname:string,mode:string,env:any)

  • chunk为存有代码段的字符串
  • chunkname在代码段的名字,可以时任何字符串
  • mode为代码端的类型,当等于't'时代表文本,'b'代表二进制,'bt'代表二进制和文本
  • env就是前面所说的_ENV环境,也就是load加载的这些代码段的运行环境
local env = {a = 14,print = print}
a = 12
local str = "print(a)"
load(str,"t","t",env)()
-- 14
复制代码

首先我们自定义了_ENV环境,并将其传给了load函数,所以load加载运行时的环境为我们自定义的_ENV环境,所以此时结果为14.

参考