作用域:局部 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

incget 共享同一个 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(拿不到)

详见 第 11 篇模块与 require

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