Lua 上值(UpValue)

Lua 上值(UpValue)

一、核心结论

UpValue(上值)就是:闭包函数引用的、定义在它外部作用域中的变量。

例如:

lua 复制代码
function CreateCounter()
    local count = 0

    return function()
        count = count + 1
        return count
    end
end

这里内部匿名函数使用了外层的 count

虽然 CreateCounter() 执行结束后,count 按普通局部变量来说应该离开作用域,但这个变量仍然被返回的闭包使用。

这个 count 就是:

UpValue

可以简单理解成:

text 复制代码
外层局部变量
    ↓
被内部函数引用
    ↓
变成闭包的 UpValue
    ↓
外层函数结束后依然存活

二、为什么需要 UpValue?

主要是为了实现 Lua 的闭包(Closure)

例如:

lua 复制代码
function CreateCounter()
    local count = 0

    return function()
        count = count + 1
        return count
    end
end

local counter = CreateCounter()

print(counter()) -- 1
print(counter()) -- 2
print(counter()) -- 3

这里:

lua 复制代码
count

属于 CreateCounter 的局部变量。

CreateCounter() 已经执行完了,count 仍然需要保存。

因此 Lua 会让这个变量继续存活,并由闭包持有。


三、UpValue 与普通局部变量的区别

普通局部变量:

lua 复制代码
function Test()
    local a = 10
end

a 只在 Test 的生命周期内有效。

而 UpValue:

lua 复制代码
function Test()
    local a = 10

    return function()
        print(a)
    end
end

这里 a 被内部函数引用,因此它需要在外层函数结束后继续存在。

可以理解为:

text 复制代码
普通局部变量:

函数栈帧
┌──────────┐
│ a = 10   │
└──────────┘
函数结束 → 消失


UpValue:

函数栈帧
┌──────────┐
│ a = 10   │
└────┬─────┘
     │
     ▼
   UpValue
     ▲
     │
   Closure

四、UpValue 和 Closure 的关系

这是面试最容易问的一组概念。

Closure 是什么?

闭包可以简单理解成:

函数 + 它所引用的外部变量

例如:

lua 复制代码
function Create()
    local x = 100

    return function()
        return x
    end
end

可以理解为:

text 复制代码
Closure
├── Function
└── UpValue
      └── x = 100

所以:

UpValue 是 Closure 保存外部变量的机制。


五、UpValue 的一个重要特点:共享

这个知识点很容易出面试题。

例如:

lua 复制代码
function Create()
    local value = 0

    local function Add()
        value = value + 1
    end

    local function Get()
        return value
    end

    return Add, Get
end

然后:

lua 复制代码
local add, get = Create()

add()
add()

print(get()) -- 2

为什么 get() 能拿到 2

因为:

text 复制代码
Add
 ↓
共同引用
 ↓
UpValue(value)
 ↑
共同引用
 ↑
Get

Add 和 Get 共享的是同一个 UpValue,而不是各自复制了一份 value

这点在理解 Lua 闭包实现时非常重要。


六、UpValue 的"开放"和"闭合"

如果面试官问得比较深入,可能会问:

Lua 底层是怎么实现 UpValue 的?

可以简单分成两个状态:

1. Open UpValue

外层函数还在执行时:

text 复制代码
局部变量还在栈上
      ↓
UpValue 指向栈上的变量

例如:

text 复制代码
Stack
┌──────────┐
│ x = 100  │ ← UpValue 指向这里
└──────────┘

2. Closed UpValue

外层函数执行结束后:

text 复制代码
栈上的变量即将失效
        ↓
把变量复制到堆上的 UpValue
        ↓
Closure 继续引用

概念上:

text 复制代码
执行中:

Stack
  ↑
UpValue
  ↑
Closure


外层函数结束:

UpValue
  ↓
独立保存 x
  ↑
Closure

所以:

UpValue 可以理解为把外部局部变量的生命周期延长到闭包仍然需要它的时候。


七、为什么 Unity / Lua 项目要关注 UpValue?

主要是两个方面。

1. 闭包产生的额外开销

例如高频代码里:

lua 复制代码
for i = 1, 10000 do
    local callback = function()
        print(i)
    end
end

会不断创建闭包以及相关 UpValue,可能带来:

text 复制代码
内存分配
+
Lua GC 压力

所以在 Unity 游戏中,Update、循环、战斗高频逻辑里不要无意义地大量创建闭包。


2. UpValue 会影响对象生命周期

例如:

lua 复制代码
function Create()
    local hugeTable = CreateHugeTable()

    return function()
        return hugeTable
    end
end

只要返回的闭包还存在:

text 复制代码
Closure
  ↓
UpValue
  ↓
hugeTable

那么 hugeTable 仍然可能被引用。

因此:

一个闭包如果无意中持有大型对象,会导致对象不能及时被 GC 回收。

这在大型 Lua 项目中属于比较典型的内存问题。


八、面试高频追问

Q1:UpValue 是什么?

闭包引用的外层作用域中的变量,就是 UpValue。


Q2:UpValue 和 Closure 有什么关系?

Closure 是函数和相关环境的组合,UpValue 是 Closure 用来保存外部变量引用的机制。


Q3:为什么外层函数执行完,UpValue 还存在?

因为被闭包引用的局部变量生命周期被延长了。Lua 会让它脱离原来的栈生命周期,继续由 UpValue 持有。


Q4:多个闭包会共享 UpValue 吗?

会。同一个外层局部变量被多个闭包引用时,它们可以共享同一个 UpValue,因此一个闭包修改变量,另一个闭包也能看到修改结果。


Q5:UpValue 会造成 GC 问题吗?

会。大量创建闭包会增加 Lua 对象和 GC 压力;如果闭包长期持有大型 table 或对象,还可能延长这些对象的生命周期,导致内存占用增加。


面试记忆关键词

闭包 → 外部变量 → UpValue → 延长生命周期 → 多闭包共享 → Open/Closed → GC


  • 🎬 博客主页:https://xiaoy.blog.csdn.net

  • 🎥 本文由 呆呆敲代码的小Y 原创 🙉

  • 🎄 学习专栏推荐:Unity系统学习专栏

  • 🌲 游戏制作专栏推荐:游戏制作

  • 🌲Unity实战100例专栏推荐:Unity 实战100例 教程

  • 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

  • 📆 未来很长,值得我们全力奔赴更美好的生活✨

  • ------------------❤️分割线❤️-------------------------

资料白嫖,技术互助

学习路线指引(点击解锁) 知识定位 人群定位
🧡 Unity系统学习专栏 入门级 本专栏从Unity入门开始学习,快速达到Unity的入门水平
💛 Unity实战类项目 进阶级 计划制作Unity的 100个实战案例!助你进入Unity世界,争取做最全的Unity原创博客大全。
❤️ 游戏制作专栏 难度偏高 分享学习一些Unity成品的游戏Demo和其他语言的小游戏!
💚 游戏爱好者万人社区 互助/吹水 数万人游戏爱好者社区,聊天互助,白嫖奖品
💙 Unity100个实用技能 Unity查漏补缺 针对一些Unity中经常用到的一些小知识和技能进行学习介绍,核心目的就是让我们能够快速学习Unity的知识以达到查漏补缺
相关推荐
事已至此先睡覺吧42 分钟前
第三篇:Java 流程控制详解:条件判断、循环与跳转语句
java·开发语言
m0_734571761 小时前
深入理解5g <二十> dmrs
开发语言·5g
liulilittle1 小时前
麻将服务器崩溃补偿与异常处理语义(CRASH_SEMANTICS)
ai·自动化·llm·mock·lua·测试·tools
liulilittle1 小时前
麻将 UI 层可观察行为(UI_BEHAVIOR)
ai·自动化·llm·mock·lua·测试·tools
金金计较.1 小时前
Go语言-4
开发语言·golang
泡海椒2 小时前
jquick-pdf 表格行列尺寸控制实战:单元格合并的可行边界
java·开发语言·pdf
纪念 2292 小时前
c++类和对象(四)
开发语言·c++
辛苦才能3 小时前
C++多态原理:虚函数表的内存布局与动态绑定的汇编真相
开发语言·c++
铲灰3 小时前
做个总结而已
开发语言
IvanCodes3 小时前
Python 文件操作(十二):文件与目录的读写
开发语言·python