前言
在lua语言中,函数是严格遵循词法定界的第一类值。
- 第一类值: 意味着lua语言中的函数与其他常见类型的值具有同等权限:一个程序可以将某个函数保存到变量中或表中,也可以将某个函数作为参数传递给其他函数,还可以将某个函数作为其他函数的返回值返回。
- 词法定界: 意味着lua语言中的函数可以访问包含其自身的外部函数中的变量。
函数是第一类值
如前所诉,lua语言中的函数是第一类值。以下示例演示了第一类值的含义:
a = {p = print} -- 'a.p'指向'print'函数
a.p("Hello World") --> Hello World
print = math.sin -- 'print'现在指向sin函数
a.p(print(1)) --> 0.8414709848079
math.sin = a.p -- 'sin'现在指向print函数
math.sin(10, 20) -->10 20
- 函数常见定义方式(语法糖模式):
function foo(x) return 2*x end
- 另一种定义方式:
foo = function(x) return 2*x end
词法定界
当编写一个被其他函数B包含的函数A时,被包含的函数A可以访问包含其他函数B的所有局部变量,我们将这种特性称为词法定界。 我们先看一个简单的例子。假设有一个表,其中包含了学生的姓名和对应的成绩,如果我们想基于分数对学生姓名排序,分数高者在前,那么可以使用如下的代码完成上诉需求:
name = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Pual = 7, Peter = 8};
table.sort(names, function(n1, n2)
return grades[n1] > grades[n2] --比较分数
end)
现在,假设我们想创建一个函数来完成这个需求:
function sortByGrade(names, grades)
table.sort(names, function(n1, n2))
return grades[n1] > grades[n2] --比较分数
end)
end
在后一个示例中,有趣的一点在于传给sort的匿名函数可以访问grades,而grades是包含匿名函数的外层函数sortByGrade的形参。在该匿名函数中,grades既不是全局变量也不算局部变量,而是我们所说的非局部变量(也被称为上值UpValues) 这一点之所以如此有趣是因为,函数作为第一类值,能够逃逸出它们的变量的原始定界范围。考虑如下代码:
function newCounter()
local count = 0
return function() --匿名函数
count = count + 1
return count
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
上诉代码中,匿名函数访问了一个非局部变量(count)并将其当作计数器。然而,由于创建变量的函数(newCounter)已经返回,因此当我们调用匿名函数时,变量count似乎已经超出了作用范围。其实不然,由于闭包概念的存在,lua语言能够正确应对这种情况。简单来说,一个闭包就是一个函数外加能够使该函数正确访问非局部变量所需的其他机制。 如果我们再次调用newCounter,那么一个新的局部变量count和一个新的闭包会被创建出来,这个新的闭包针对的是这个新变量。
c2 = newCounter()
print(c2()) --> 1
print(c3()) --> 3
print(c2()) --> 2
总结
UpValue实际是局部变量,而局部变量是保存在函数堆栈框架上的,所以只要UpValue没有离开自己的作用域,它就一直生存在函数堆栈上。这种情况下,闭包将通过堆栈上的UpValue的引用来访问它们,一旦UpValue即将离开自己的作用域,在从堆栈上消失之前,闭包就会为它分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该UpValue。