作用域:局部 vs 全局
x = 1 -- 全局
local y = 2 -- 局部
print(_G.x) -- 1 (全局表 _G)
print(_G.y) -- nil
默认全局是 Lua 的坑王。漏写 local → 静默污染全局空间,相隔很远的代码互相覆盖。
强制要求 local
把这行加到 init.lua:
setmetatable(_G, {
__newindex = function(_, k, v)
error("不许写全局:" .. k, 2)
end
})
之后凡是漏 local 都报错。
词法作用域 + 块
do ... end / if then end / for / while / repeat / 函数体——都是块:
local x = 1
do
local x = 2
print(x) -- 2(内层覆盖)
end
print(x) -- 1(外层)
变量在声明位置之后到块结束可见。
upvalue(闭包捕获的变量)
local function make_adder(n)
return function(x) return x + n end
end
local add5 = make_adder(5)
print(add5(10)) -- 15
内层函数引用了外层的 n——n 就是该闭包的 upvalue。
多个闭包共享 upvalue
local function counter()
local n = 0
local function inc() n = n + 1; return n end
local function get() return n end
return inc, get
end
local inc, get = counter()
inc(); inc(); inc()
print(get()) -- 3
inc 和 get 共享同一个 n——它们是一对闭包,引用同一份 upvalue。
upvalue 是变量,不是值
local function make_funcs()
local i = 0
local fns = {}
for k = 1, 3 do
fns[k] = function() return k end
end
return fns
end
local fns = make_funcs()
print(fns[1](), fns[2](), fns[3]()) -- 1 2 3
每次循环 k 重新声明(5.4 行为)。在 5.1 / LuaJIT,for 循环变量在每次迭代都是新作用域——同样得到 1, 2, 3。与 JS 的 var 陷阱不同,Lua 这里行为符合直觉。
局部变量更快
-- 慢:每次循环查全局 _G
for i = 1, 1e7 do
print(i)
end
-- 快:局部捕获 print
local print = print
for i = 1, 1e7 do
print(i)
end
全局变量是 _G.X 查表;局部是栈上偏移。性能敏感场景把外部函数缓存成 local:
local insert = table.insert
local floor = math.floor
模块的私有变量
文件顶层的 local 对外不可见——这就是 Lua 的"模块私有":
-- mymod.lua
local M = {}
local SECRET = "hidden" -- 模块私有
function M.greet()
return "Hello with " .. SECRET
end
return M
local m = require("mymod")
m.greet() -- 能用
m.SECRET -- nil(拿不到)
upvalue 数量上限
每个闭包最多 ~200 个 upvalue(实现限制)。超过会报错。实际不会碰到——超过通常意味着设计需要重构。
检视 upvalue
local function f() local x = 10; return function() return x end end
local g = f()
print(debug.getupvalue(g, 1)) -- x 10
debug.setupvalue(g, 1, 999)
print(g()) -- 999
调试 / 反射用。生产代码不要直接改 upvalue。
→ 下一篇 Metatables