lua 能够执行不同文件中的 lua 代码,这些分散在不同 .lua 文件中的 lua 代码所创建和使用的 lua 变量,它们有着不同的作用范围,本文将结合 lua 编译器行为进行详细分析。
chunk 和 block
chunk
chunk 指一段 lua 能够执行的代码,这段代码可能有一行,也可能有多行。
在 lua 解释器的交互环境中,lua 视每一行用户输入为一个 chunk*。
当 *lua 执行 .lua 文件时,lua 视整个文件为一个 chunk*。
*lua 以 chunk 为单位执行 lua 代码。block
lua 以 block 来区分变量的作用范围。
block 分三类:
body of a control structure (控制结构体),包括if then else
、while
、repeat
、for
、do end
等。
body of a function(函数体),函数定义与函数结束之间。
*chunk。根据 *chunk 定义,chunk 内能够包含任何合法的 lua 代码,chunk 内部能够包含多个控制结构体和函数体,因此,定义在 chunk 内的变量能够被内部其他 block 使用。
局部变量、自由名称和上游值
lua 变量不需要 declaration 声明,赋值即创建,不限使用(尽力取值),如:lua 变量的作用范围是 block,在变量作用范围的说明中,涉及局部变量、自由名称和上游值的概念,现一一说明。b = 10 -- 创建变量 b
print(a) -- 变量 a 不存在,返回 nil局部变量
局部变量 local variable 的创建形式为local varname = ?
。局部变量的作用范围是创建时,该变量所在的 block。自由名称
自由名称 free name 的创建形式为freename = ?
。需要注意的是,该变量在之前并没有被创建,如:自由名称由 lua compiler 编译器通过内部机制转换成类局部变量,对整个 chunk 起作用。local a = 5
a = 4 -- a 在之前已创建,a 是局部变量
b = "hello" -- b 是自由名称上游值
上游值 upvalue*,由 *lua compiler 管理和设置。
当 lua 执行 chunk 时,lua compiler 会为该 chunk 创建一个局部变量_ENV
,并赋值_ENV = upvalue
。chunk 内的全部自由名称的访问将被转换为对局部变量_ENV
成员的访问,例如:a = 5
print(b)
-- 将被转换为
_ENV.a = 5
print(_ENV.b)
变量
_ENV
环境变量
类似于操作系统的环境变量,在 lua 运行环境中,_ENV
_ENV
的值确定了当前 chunk 的运行环境。可通过如下代码查看 lua 默认 upvalue:a = 5
print(a)
-- 真实的变量访问
_ENV.a = 5
_ENV.print(_ENV.a)执行上述代码后,可看到熟悉的名称,如:package、math、string、print、pairs 等。可以看出,有了for k, v in pairs(_ENV) do
print(k, v)
end_ENV
的支持,才能简洁无障碍的使用这些系统 module 和 function。可控环境
chunk 运行依赖_ENV
_ENV
,通过_ENV
设置可实现 lua 运行环境的隔离和修改。
改变默认_ENV
成员,裁减、增加或修改所需的 module 或 function*,改变 *chunk 级别的运行环境,如:创建局部变量-- 删除 dofile 函数
dofile = nil
_ENV.dofile = nil
-- 增加自定义模块
mymodule = require("modulename")
_ENV.mymodule = _ENV.require("modulename")
-- 修改 print 函数
print = function() --[[your code]] end
_ENV.print = function() --[[your code]] end
-- 也可进行整体替换
_ENV = {print = print} -- 该环境只支持 print 函数_ENV
,改变 block 级别的运行环境,如:do
-- _ENV 仅在此 block 有效
local _ENV = {...}
...
end
function f(para, _ENV)
-- 以函数参数形式使用 _ENV
end
function f(para)
-- 以局部变量的形式使用 _ENV
local _ENV = {...}
endfree name 传导和
lua 运行时,默认 upvalue 会因 free name 的创建而发生改变,而这种改变将传导至下一次的 upvalue 使用,从而出现全局变量的效果。如:_ENV._G
成员从执行效果看,以 upvalue 为桥梁,free name 的使用会出现全局变量的效果。-- chunk1 c1.lua
a = 5
-- chunk2 c2.lua
print(a)
-- chunk3 c3.lua
dofile("c1.lua") -- 执行 chunk1,遇到 free name:a,创建 upvalue.a,upvalue.a = 5
print(a) -- upvalue 已被 chunk1 改变,upvalue.a 值为 5
dofile("c2.lua") -- upvalue 传导至 chunk2,获取 upvalue.a
lua 默认_ENV
包含_ENV._G
成员,而_ENV._G
的值为默认 upvalue*,即_ENV == _ENV._G
,因此,可以通过_G.x
来引用“全局变量”x
。
需要注意的是,无节制的使用 *free name 将导致 upvalue 的成员过多,影响 upvalue 传导时的效率。实际编程过程中,可使用_G
来明确全局变量的使用,本地使用的变量一律用local
来修饰,如:此外,如果local a = 5 -- 局部变量 a
local b = 6 -- 局部变量 b
_G.c = 20 -- 显式使用全局变量 c_ENV
的值发生改变,_ENV._G
可能不存在,因此,在改变_ENV
之前,可事先保存_ENV
或者_ENV._G
,如:local g = _G -- _ENV 未改变,保存 _G 至局部变量 g
_ENV = {print = print, a = "hello"} -- 新 _ENV 只包含两个成员:_ENV.print 和 _ENV.a
结束语
lua 局部变量的作用范围是 block*。
变量_ENV
由 *lua compiler 在执行 chunk 前创建,可理解为该 chunk 的局部变量,chunk 内对 free name 的访问被转换为对_ENV
对应成员的访问。ENV
的默认值为 upvalue*。
*lua 没有全局变量,lua 通过 upvalue 和_ENV
实现全局变量的效果。