全局环境的一个问题是, 任何修改都会影响你的程序的所有部分。例如,当你安装一个metatable去控制全局访问时,你的整个程序都必须遵循同一个指导方针。如果你想使用标准库,标准库中可能使用到没有声明的全局变量,你将碰到坏运。
Lua 5.0允许每个函数可以有自己的环境来改善这个问题,听起来这很奇怪;毕竟,全局变量表的目的就是为了全局性使用。然而在Section 15.4我们将看到这个机制带来很多有趣的结构,全局的值依然是随处可以获取的。
可以使用setfenv函数来改变一个函数的环境。Setfenv接受函数和新的环境作为参数。除了使用函数本身,还可以指定一个数字表示栈顶的活动函数。数字1代表当前函数,数字2代表调用当前函数的函数(这对写一个辅助函数来改变他们调用者的环境是很方便的)依此类推。下面这段代码是企图应用setfenv失败的例子:
a = 1 -- create a global variable -- change current environment to a new empty table setfenv(1, {}) print(a)
导致:
stdin:5: attempt to call global `print' (a nil value)
(你必须在单独的chunk内运行这段代码,如果你在交互模式逐行运行他,每一行都是一个不同的函数,调用setfenv只会影响他自己的那一行。)一旦你改变了你的环境,所有全局访问都使用这个新的表,如果她为空,你就丢失所有你的全局变量,甚至_G,所以,你应该首先使用一些有用的值封装(populate)她,比如老的环境:
a = 1 -- create a global variable -- change current environment setfenv(1, {_G = _G}) _G.print(a) --> nil _G.print(_G.a) --> 1
现在,当你访问"global" _G,他的值为旧的环境,其中你可以使用print函数。
你也可以使用继承封装(populate)你的新的环境:
a = 1 local newgt = {} -- create new environment setmetatable(newgt, {__index = _G}) setfenv(1, newgt) -- set it print(a) --> 1
在这段代码新的环境从旧的环境中继承了print和a;然而,任何赋值操作都对新表进行,不用担心误操作修改了全局变量表。另外,你仍然可以通过_G修改全局变量:
-- continuing previous code a = 10 print(a) --> 10 print(_G.a) --> 1 _G.a = 20 print(_G.a) --> 20
当你创建一个新的函数时,他从创建他的函数继承了环境变量。所以,如果一个chunk改变了他自己的环境,这个chunk所有在改变之后定义的函数都共享相同的环境,都会受到影响。这对创建命名空间是非常有用的机制,我们下一章将会看到。