[lua]upvalue和闭包

概念

当一个函数内部A嵌套另一个函数B定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称为词法定界,B中被A访问到的变量我们称为upvalue。而闭包是指一个函数加上它可以正确访问到的upvalue。技术上来讲,运行时,我们执行的是闭包,而不是函数,函数仅仅是闭包的一个原型声明;闭包在完全不同的上下文中也是很有用途的。因为函数被存储在普通的变量内我们可以很方便的重定义或预定义函数。通常当你需要原始函数有一个新的实现时可以重定义函数。upvalue你可以理解成是一块特殊的内存,独立管理内部数据的,所以可以在多个闭包之间提供一种数据共享的机制。

示例解释

upvalue

function f1(n)
    local function f2()
        print(n)
    end
    return f2
end
local g1 = f1(11)
g1()                    --打印11
local g2 = f2(22)
g2()                    --打印22
g1()                    --打印11

在上面的例子里,我们可以看出来当g1 = f1(11)这个被执行完的时候,局部变量n的生命周期应该是结束了的。但是因为它成为了f2函数的upvalue,所以它的生命周期跟着g1的生命周期一起了,被延长了。下面的g2是重新执行了一遍f2所以它重新包了一个n做upvalue,这个时候upvalue的数据没有共享。

闭包

function f1(n)
    local function f2()
        print(n)
    end
    n = n+10
    return f2
end
g1 = f1(10)
g1()                    --打印20

在上面的这个例子里,我们发现n在包在f2内部的时候,成为f2的upvalue,但f1还没执行完成n还是f1的局部变量,所以执行了n = n+10的这个的时候,n的变化也在f2执行的时候被表现出来的。

upvalue的数据共享

function f1(n)
    local function f2()
        print(n)
    end
    local function f3()
        n = n +10
    end
    n = n+1
    return f2,f3
end
local g2,g3 = f1(1)
g2()                        --打印2
g3()
g2()                        --打印12

从上面的例子我们可以发现,当f1执行结束之后,lua会把内部所引用的n做成一个upvalue来作为f2和f3的upvalue,所以当我们g3调用了n = n+10的时候,n的改变也就改了那共同upvalue内的n,所以可以被g2函数所打印出来变化。

function f1(n)
    local function f4()
        local function f2()
            print(n)
        end
        local function f3()
            n = n +10
        end
        return f2,f3
    end
    n = n+1
    return f4
end
local g1 = f1(1)
local g2,g3 = g1()
g2()                    --2
g3()
g2()                    --12

所以我们就很容易理解上面这个例子为啥还是一样的打印结果,因为upvalue在一个函数内就只会有一份相同的。

总结

闭包这个概念在很多语法中都有的,之前有一点点了解,最近在做一些lua的内存泄漏优化的时候,发现因为一些全局表的函数引用,导致了一些传入进去的值被引用到,没办法释放,引起了一些内存上的增长,所以我们在给函数传值的时候,需要注意到这个函数什么时候被释放的,是不是不会被释放掉,如果它在一个全局table中,那么它所引用到的值我们就需要手动释放。关于upvalue的lua中是怎么实现的,之后有机会讲解一下源码。