维基百科----闭包
In programming languages, a closure (also lexical closure or function closure) is a technique for implementing lexically scoped name binding in a language with first-class functions.
从概念角度看,在具备 first-class functions 的编程语言中,闭包是一种实现 词法作用域级别的名字绑定 的技术。
Operationally, a closure is a record storing a function together with an environment.
从实现角度看,闭包是保存 函数 + 上下文环境 的记录。
到此我们看到一条关于闭包的定义: 闭包=函数+上下文环境
想要理解这句话,我们就要知道两个概念
- 函数
- 上下文环境
函数大家都能理解,我们继续看看维基百科对于上下文环境的定义和分析
The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.
(上下文)环境是 自由变量名字 与该名字的 值/引用 的映射(name -> value),注意变量的值、引用是闭包 创建 时确定的。
自由变量:
- 在函数的 enclosing scope 中定义,但在函数内部使用的变量;
- 既不在函数参数列表中,也不是函数局部变量的变量;
A closure—unlike a plain function—allows the function to access those captured variables through the closure’s copies of their values or references, even when the function is invoked outside their scope.
被捕获变量:即闭包使用的自由变量,闭包中一旦引用自由变量,则称该变量被闭包捕获。
若语言不支持 first-class functions,则闭包与普通函数并无区别,若支持 first-class function 则两者区别巨大:
- 闭包的上下文环境会保存被捕获的自由变量值、引用的 副本;
- 闭包作为函数返回值返回后,调用闭包,闭包中依然保存其 定义时 的自由变量的值;
这里需要注意的是,闭包捕获的是变量本身,即是对变量本身的引用,这意味着:
- 闭包外部对自由变量的修改,在闭包内部是可见的;
- 闭包内部对自由变量的修改,在闭包外部也是可见的。
- 这里的修改是指重新给变量赋值而非重新声明变量
总结如下:
- 闭包 = 函数 + 上下文环境
- 上下文环境 = 自由变量名字 -> 值/引用(key -> value)
- 自由变量 = 在函数的 enclosing scope 定义,在函数内部引用的变量
- 上下文环境中保存的 value/reference 是闭包 定义 时的 副本
- 不同场景中,自由变量的值可以不同,进而闭包也可以不同
如此一来对于闭包的理解大概可以解释成:
“first-class function with lexical scope”,即具备词法作用域的第一等函数
- 词法作用域
一言以蔽之,“作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)的规则”。在这句话中读到一个关键点 查找变量(标识符)而词法作用域是静态的作用域,在你书写代码时就确定了。 - 第一等函数
在 Scala 中函数是一等公民,这意味着不仅可以定义函数并调用它们,还可以将它们作为值进行传递
我的理解
到此我们将维基百科的概念看的差不多了,要说一说上面的总结,将以上总结成一句话就是:
闭包是一个函数,是一个包含自由变量的函数,我们在修改自由变量时,函数也会发生改变。
OR 返回值依赖于声明在函数外部的一个或多个变量,那么这个函数就是闭包函数。
看一个栗子
var more = 10
// addMore 一个闭包函数:其捕获了自由变量 more
val addMore = (x: Int) => x + more
上面的代码中有两个参数,一个是绑定的参数x,需要人为的传入,还有一个自由变量more,本身more是没有任何意义的,但是在执行函数addMore时,在上下文中捕获了这一变量,且依赖于这一变量,所以他是一个闭包函数
在看一个栗子
object Closures {
def main(args: Array[String]): Unit = {
val addone = makeadd(1) //我们伪造more自由变量1、2
val addtwo = makeadd(2)
println(addone(1))//赋值x=1
println(addtwo(1))
}
def makeadd(more: Int) = (x :Int) => x+ more //捕获more
}
我们定义了一个函数 makeAdd,输入参数是 Int 类型,返回的是一个函数(其实可以看成函数,后面我们会深入去研究到底是什么),main 方法中,首先我们通过调用 makeAdd 来定义了两个 val:addOne 和 addTwo 并分别传入 1 和 2,然后执行并打印 addOne(1) 和 addTwo(2),运行的结果是 2 和 3。
这样就引出了闭包的另一种定义方式:
在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。
我们可以理解成上面代码的addone和addtwo,即是两个闭包函数
修改自由变量和重新声明的区别
1、修改自由变量
// 声明 more 变量
scala> var more = 10
more: Int = 10
// more 变量必须已经被声明,否则下面的语句会报错
scala> val addMore = (x: Int) => {x + more}
addMore: Int => Int = $$Lambda$1076/1844473121@876c4f0
scala> addMore(10)
res7: Int = 20
// 注意这里是给 more 变量赋值,而不是重新声明 more 变量
scala> more=1000
more: Int = 1000
scala> addMore(10)
res8: Int = 1010
可以看出,闭包函数捕获的自由变量也跟着改变了,输出结果发生变化
2、重新声明变量
// 第一次声明 more 变量
scala> var more = 10
more: Int = 10
// 创建闭包函数
scala> val addMore10 = (x: Int) => {x + more}
addMore10: Int => Int = $$Lambda$1077/1144251618@1bdaa13c
// 调用闭包函数
scala> addMore10(9)
res9: Int = 19
// 重新声明 more 变量
scala> var more = 100
more: Int = 100
// 创建新的闭包函数
scala> val addMore100 = (x: Int) => {x + more}
addMore100: Int => Int = $$Lambda$1078/626955849@4d0be2ac
// 引用的是重新声明 more 变量
scala> addMore100(9)
res10: Int = 109
// 引用的还是第一次声明的 more 变量
scala> addMore10(9)
res11: Int = 19
// 对于全局而言 more 还是 100
scala> more
res12: Int = 100
在重新声明了more变量之后,原来的闭包函数并没有发生变化,这是闭包函数中的副本机制:
自由变量可能随着程序的改变而改变,从而产生多个副本,但是闭包永远指向创建时候有效的那个变量副本。
这是由虚拟机来实现的,虚拟机会保证 more 变量在重新声明后,原来的被捕获的变量副本继续在堆上保持存活。
怀疑柯里化是闭包的一种惯用方法???