提示
本篇笔记中的大部分示例必须以单段代码的方式运行。如果在交互模式下一行一行的输入代码,那么每一行代码都会变成一段独立的代码,因此每一行都会有一个不同的 _ENV
变量。为了把代码当做一个代码段运行,要么把代码保存在一个文件中运行,要么使用 do-end
将代码段包围起来。
由于 _ENV
只是一个普通的变量,因此可以对其赋值或像访问其他变量一样访问它。赋值语句 _ENV = nil
会使得后续代码不能直接访问全局变量。这可以用来控制代码使用那种变量:
local print, sin = print, math.sin _ENV = nil print(13) --> 13 print(sin(13)) --> 0.42016703682664 print(math.cos(13)) --> ERROR: attempt to index a nil value (upvalue '_ENV')点击复制复制失败已复制
任何对自由名称("全局变量")的赋值都会引发类似的错误。
我们可以显式地使用 _ENV
来绕过局部声明:
a = 13 -- 全局的 local a = 12 print(a) --> 12 (局部的) print(_ENV.a) --> 13 (全局的)
用 _G
也可以:
a = 13 -- 全局的 local a = 12 print(a) --> 12 (局部的) print(_G_.a) --> 13 (全局的)
通常, _G
和 _ENV
指向的是同一个表。但是,尽管如此,他们是很不一样的实体。 _ENV
是一个局部变量,所有对"全局变量"的访问实际上访问的都是 _ENV
。 _G
则是一个在任何情况下都没有任何特殊状态的全局变量。按照定义, _ENV
永远指向的是当前的环境;而假设在可见且无人改变过其值的前提下, _G
通常指向的是全局环境。
_ENV
的主要用途是用来改变代码段使用的环境。一旦改变了环境,所有的全局访问就都将使用新表:
-- 将当前的环境改为一个新的空表 _ENV = {} a = 1 -- 在_ENV中创建字段 print(a) --> attempt to call a nil value (global 'print')
如果新环境是空的,就会丢失所有的全局变量,包括函数 print
。因此,应该首先把一些有用的值放入新环境,比如全局环境:
a = 15 -- 创建一个全局变量 _ENV = {g = _G} -- 改变当前环境 a = 1 -- 在_ENV中创建一个字段 g.print(_ENV.a, g.a) --> 1 15
这时,当访问"全局"的 g
(位于 _ENV
而不是全局环境中)时,我们使用的是全局环境,在其中能够找到函数 print
。
我们可以使用 _G
代替 g
,从而重写前面的例子:
a = 15 -- 创建一个全局变量 _ENV = {_G = _G} -- 改变当前环境 a = 1 -- 在_ENV中创建一个字段 _G.print(_ENV.a, _G.a) --> 1 15
_G
只有在 Lua
语言创建初始化的全局表并让字段 _G
指向它自己的时候,才会出现特殊状态。 Lua
语言并不关心该变量的当前值。不过尽管如此,就像我们在上面重写的示例中所看到的那样,将指向全局环境的变量命名为同一个名字( _G
)是一个惯例。
另一种把旧环境装入新环境的方式是使用继承:
a = 1 local newgt = {} -- 创建新环境 setmetatable(newgt, {__index = _G}) _ENV = newgt -- 设置新环境 print(a) --> 1
在这段代码中,新环境从全局环境中继承了函数 print
和 a
。不过,任何赋值都会发生在新表中。虽然我们仍然能通过 _G
来修改全局环境中的变量,但如果误改了全局环境中的变量也不会有什么影响。
a = 1 local newgt = {} -- 创建新环境 setmetatable(newgt, {__index = _G}) _ENV = newgt -- 设置新环境 print(a) --> 1 a = 10 print(a, _G.a) --> 10 1 _G.a = 20 print(_G.a) --> 20
作为一个普通的变量, _ENV
遵循通常的定界规则。特别地,在一段代码中定义的函数可以按照访问其他外部变量一样的规则访问 _ENV
:
_ENV = {_G = _G} local function foo () _G.print(a) end a = 10 foo() --> 10 _ENV = {_G = _G, a = 20} foo() --> 20
如果定义一个名为 _ENV
的局部变量,那么对自由名称的引用将会绑定到这个新变量上:
a = 2 do local _ENV = {print = print, a = 14} print(a) --> 14 end print(a) --> 2 (回到原始的_ENV中)点击复制复制失败已复制
因此,可以很容易地使用私有环境定义一个函数:
function factory (_ENV) return function () return a end end f1 = factory{a = 6} f2 = factory{a = 7} print(f1()) --> 6 print(f2()) --> 7
factory
函数创建了一个简单的闭包,这个闭包返回了其中"全局"的 a
。每当闭包被创建时,闭包可见的变量 _ENV
就成了外部 factory
函数的参数 _ENV
。因此,每个闭包都会使用自己对外部变量(作为上值)来访问其自由名称。
使用普通的定界规则,我们可以有几种方式操作环境。例如,可以让多个函数共享一个公共环境,或者让一个函数改变它与其他函数共享的环境。