14.3 非全局的环境

全局环境的一个问题是,任何修改都会影响你的程序的所有部分。例如,当你安装一个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

在这段代码新的环境从旧的环境中继承了printa;然而,任何赋值操作都对新表进行,不用担心误操作修改了全局变量表。另外,你仍然可以通过_G修改全局变量:

-- continuing previous code

a = 10

print(a)      --> 10

print(_G.a)   --> 1

_G.a = 20

print(_G.a)   --> 20

当你创建一个新的函数时,他从创建他的函数继承了环境变量。所以,如果一个chunk改变了他自己的环境,这个chunk所有在改变之后定义的函数都共享相同的环境,都会受到影响。这对创建命名空间是非常有用的机制,我们下一章将会看到。

 

 



相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛