闭包是什么,有什么特性,对页面有什么影响

Python010

闭包是什么,有什么特性,对页面有什么影响,第1张

闭包是可以包含自由(未绑定到特定对象变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。在 Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等语言中都能找到对闭包不同程度的支持。

闭包 (closure)是个精确但又很难解释的电脑名词。在 Perl 里面,闭包是以 匿名函数的形式来实现,具有持续参照位于该函数范围之外的文字式变数值的能力。这些外部的文字变数会神奇地保留它们在闭包函数最初定义时的值 (深连结)。

如果一个程式语言容许函数递回另一个函数的话 (像 Perl 就是),闭包便具有意义。要注意的是,有些语言虽提供匿名函数的功能,但却无法正确处理闭包; Python 这个语言便是一例。如果要想多了解闭包的话,建议你去找本功能性程式 设计的教科书来看。Scheme这个语言不仅支持闭包,更鼓励多加使用。

以下是个典型的产生函数的函数:

sub add_function_generator {

return sub { shift + shift }

}

$add_sub = add_function_generator()

$sum = &$add_sub(4,5); # $sum是 9了

闭包用起来就像是个函数样板,其中保留了一些可以在稍後再填入的空格。add_function_generator() 所递回的匿名函数在技术上来讲并不能算是一个闭包, 因为它没有用到任何位在这个函数范围之外的文字变数。

把上面这个例子和下面这个make_adder()函数对照一下,下面这个函数所递回的匿名函数中使用了一个外部的文字变数。这种指明外部函数的作法需要由 Perl递回一个适当的闭包,因此那个文字变数在匿名函数产生之时的值便永久地被锁进闭包里。

sub make_adder {

my $addpiece = shift

return sub { shift + $addpiece }

}

$f1 = make_adder(20);

$f2 = make_adder(555);

这样一来&$f1($n) 永远会是 20加上你传进去的值$n ,而&$f2($n) 将 永远会是 555加上你传进去的值$n。$addpiece的值会在闭包中保留下来。

闭包在比较实际的场合中也常用得到,譬如当你想把一些程式码传入一个函数时:

my $line

timeout(30,sub { $line = <STDIN>})

如果要执行的程式码当初是以字串的形式传入的话,即'$line = <STDIN>' ,那么timeout() 这个假想的函数在回到该函数被呼叫时所在的范围後便无法再撷取$line这个文字变数的值了。

语法结构编辑

Groovy的闭包

闭包(Closure)是Java所不具备的语法结构。闭包就是一个代码块,用“{ }”包起来。此时,程序代码也就成了数据,可以被一个变量所引用(与C语言的函数指针比较类似)。闭包的最典型的应用是实现回调函数(callback)。Groovy的API大量使用闭包,以实现对外开放。闭包的创建过程很简单,例如:

{ 参数 ->

代码...

}

参考下面的例子代码,定义了c1和c2两个闭包,并对它们进行调用:

def c1 = { println it }

def c2 = { text ->println text }

c1.call("content1") //用call方法调用闭包

c2("content2") //直接调用闭包

“->;”之前的部分为闭包的参数,如果有多个参数,之间可用逗号分割;“->;”之后的部分为闭包内的程序代码。如果省略了“->;”和它之前的部分,此时闭包中代码,可以用名为“it”的变量访问参数。

闭包的返回值和函数的返回值定义方式是一样的:如果有return语句,则返回值是return语句后面的内容;如果没有return语句,则闭包内的最后一行代码就是它的返回值。[1]

环境表达编辑

在Javascript中闭包(Closure)

什么是闭包

“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

相信很少有人能直接看懂这句话,因为他描述的太学术。我想用如何在Javascript中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段

代码

1

2

3

4

5

6

7

8

9

function a(){

var i=0

function b(){

alert(++i)

}

return b

}

var c=a()

c()

特点

这段代码有两个特点:

1、函数b嵌套在函数a内部;

2、函数a返回函数b。

这样在执行完var c=a( )后,变量c实际上是指向了函数b,再执行c( )后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:

当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。

作用

简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。

在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)

另一个例子

模拟私有变量

function Counter(start){

var count = start

return{

increment:function(){

count++

},

get:function(){

return count

}

}

}

var foo =Counter(4)

foo.increment()

foo.get()// 5

结果

这里,Counter 函数返回两个闭包,函数 increment 和函数 get。 这两个函数都维持着 对外部作用域 Counter 的引用,因此总可以访问此作用域内定义的变量 count.

文法

objective c的闭包(block)

objective c 中的的闭包,是通过block实现的。Apple在C,Objective-C和C++中扩充了Block这种文法的,并且在GCC4.2中进行了支持。你可以把它理解为函数指针,匿名函数,闭包,lambda表达式,这里暂且用块对象来表述,因为它们之间还是有些许不同的。

声明一个块

如果以内联方式使用块对象,则无需声明。块对象声明语法与函数指针声明语法相似,但是块对象应使用脱字符(^)而非星号指针 (*)。下面的代码声明一个aBlock变量,它标识一个需传入三个参数并具有float返回值的块。

float (^aBlock)(const int*, int, float)

l 创建一个块

块使用脱字符(^)作为起始标志,使用分号作为结束标志。下面的例子声明一个简单块,并且将其赋给之前声明的block变量(oneFrom)。

int (^oneFrom)(int)

oneFrom = ^(int anInt) {

return anInt - 1

}

微观世界

如 果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(execution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。

2、当函数a执行的时候,a会进入相应的执行环境(execution context)。

3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。

4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。

5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。

6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:

如图所示,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依 次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

应用场景

1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。

以上两点是闭包最基本的应用场景,很多经典案例都源于此。

回收机制

在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

匿名内部

在Python中的闭包(Closure)

学过Java GUI编程的人都知道定义匿名内部类是注册监听等处理的简洁有效手段,闭包的定义方式有点类似于这种匿名内部类,

但是闭包的作用威力远远超过匿名内部类,这也是很多流行动态语言选择闭包的原因,相信你在JavaScript中已经了解它的神奇功效了。

定义

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

简单闭包的例子:

下面是一个使用闭包简单的例子,模拟一个计数器,通过将整型包裹为一个列表的单一元素来模拟使看起来更易变:

函数counter()所作的唯一一件事就是接受一个初始化的值来计数,并将该值赋给列表count成员,然后定义一个内部函数incr()。通过内部函数使用变量count,就创建了一个闭包。最魔法的地方是counter()函数返回一个incr(),一个可以调用的函数对象。

运行:

>>>c = counter⑸

>>>type(c)

<type 'function'>

>>>print c()6

>>>print c()

7

代码格式较重要

>>>c2 = counter(99)

100

>>>print c()

8

闭包的英文对应的是Closure,如果要单纯的讨论这个概念的话就要提到和图灵机起名的大名鼎鼎的lambda演算(lamdba calculus)。尽管lamdba的概念并不是本文的重点,但是闭包概念的目的便是支持lamdba的实现。如果你单独地在百度对进行搜索闭包的话,你会发现大部分都是js相关的内容,主要是js本身就只用闭包的这个概念。但是闭包并不仅限于js,而是一个通用的概念。借用wiki中有点抽象的定义来说的话,闭包就是:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。

简单来说就是当一个方法引用了方法局部变量外的变量时,它就是一个闭包。而如果根据这个定义继续延展的话,就可以得到另外的一种描述方法:

闭包是由函数和与其相关的引用环境(方法外变量)组合而成的实体。

尽管给出了两种关于闭包是什么的定义,但是闭包本身的概念还是较为的抽象。比如直接给例子,我们可以先讨论一下为什么需要闭包。

为了理解闭包,我们可以先假设这样的一种场景:

我有一个方法A,其中有私有变量。我想在别的方法中可以直接访问并使用它,但是又不想直接暴露它。

这时候,就可以在A建立一个内部类B来访问私有变量。那么这时候,这个内部类中你所可以访问B的方法,就和A中的私有变量形成了闭包。此时B已经捕获了A中的变量。即便是A对象销毁了,被B捕获的私有变量仍然不会释放。

所以可以理解,如果希望系统 方法和当前环境的上下文绑定 的话,就可以使用闭包。

尽管有些不恰当,但是我们可以再换一个实际的场景来举个例子帮助你理解:

当你在购物的时候,你选中了一件商品,然后下单。这时候你的订单就已经绑定了你选中的商品。那么你的购买行为就和商品组成了闭包。这时候即便商家下架商品,也不会影响到商品后续的发货签收。并且这些逻辑只需要根据购买行为中的信息进行判断就可以了,而不用关心商家什么时候把商品下架,这写逻辑发生时候的商品价格是什么。

结合上面的例子,我们可以发现,当一个方法和其调用的外部环境形成闭包的时候,由于外部环境已经确定,那么我们就不用再关心外部环境了,而只用关心方法本身。

所以针对我们为什么需要闭包,我给出的答案是:

使用闭包的设计方式,由于闭包本身已经包含了上下文信息,所以可以对北向功能调用(用户)屏蔽由于环境而引发的复杂处理和交互成本。

对于Java来说,可以理解为主要是两种的闭包方式:

其中内部类除了本身的内部类还有局部内部类、匿名内部类。我们以最简洁的匿名内部类来举例:

此时say方法中便捕获了length属性,而如果你使用的是足够版本的IDE的话,获取还会提示你:

Anonymous new ISay() can be replaced with lambda

替换后:

那么这时候你就会发现,此时return的对象就是一个通过匿名后直接描述的函数,而这个函数同时还关联了上下文之外的环境信息。

而在java8中lambda的中我们可以通过函数式编程接口直接接收这种闭包,这些接口常用的为:

关于函数式接口本文就不展开了,但是利用函数式接口,我们就可以这样传递一个闭包:

此时say返回的就不是String的结果了,而是直接将闭包本身返回了。而这个闭包在创建的时候就已经绑定了所需要的环境属性。所以我们可以在需要的时候再调用闭包,并且不用再关心它到底依赖了其他哪些变量:

当然你可能会角色,这个length似乎没有什么用呀,那如果我们换一个形式:

使用Spring的依赖注入后,那么这个Closure类本身可能是各种策略模式策略器中的一个,策略器返回的是一个已经关联了具体策略路由的闭包。而当这个方法提供出去的时候,后续的调用者只需要知道这个闭包是可以针对文本进行处理的就可以,而至于之前是使用的什么策略它则不用关心。因为这些细节都已经通过闭包屏蔽了。

仅仅谈论好的而对问题闭口不谈确实不好,虽然闭包提供了强大的功能,可以对业务细节进行屏蔽,对系统进行接耦拆分。但是闭包本身确实有一些问题需要留意:

可以发现,这两个问题都是由于闭包本身的优点而产生的。由于闭包关联了环境信息,所以其让环境信息中对象的生命周期变长,这对于系统性能的维护以及jvm的垃圾回收都有负面因素。而同时因为不同于一般的编码风格,闭包的使用需要开发人员对实体进行抽象,才能比较好地实现。总结来说,对于开发人员本身有一定要求。

平时我们也经常使用lambda表达式来处理一些业务逻辑,偶尔会出现一下的情况:

先不管这段代码的实现业务背景是什么,但是IDE会提示在userIds.get(i)中的i提示的信息为:

Variable used in lambda expression should be final or effectively final

结合了上文中关于闭包的内容,我们就不难理解。由于闭包是要关联外部环境变量,在这部分代码中关联的是索引变量i,但是因为i本身是局部变量,无法保证关联环境的稳定性(我自己的理解),所以java编译器会强制的要求当闭包关联的是局部变量的时候,需要添加final关键字,而在进行final关键字从而保证该变量的内存引用不会发生改变。从本章的代码上结论上看则是进行了一次内存拷贝,来保证每个闭包中关联的环境变量不会改变,修改后的代码为:

闭包本身是一种面向抽象编程,屏蔽细节的设计原则。在良好的设计下,可以通过闭包来屏蔽对于环境信息的感知,从而简化外部对于系统理解的成本,提高系统的易用性。

这篇文章翻译而来。 这个问题是在最近一次英格兰Brighton ALT.NET Beers活动中提出来的。我发现,如果不用代码来演示,你很难单用话语把它解释清楚,所以,在这里,我打算用C#来解释一下什么是闭包(closures)。维基百科上说:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 所以,一个闭包就是一个“捕获”或“携带”了其被生成的环境中、所属的变量范围内所引用的所有变量的函数。的确,很难描述,但当你看完了这些代码后,你就很容易理解了。var x = 1

Console.Out.WriteLine("result = {0}", result)}action()这里我们首先定义了一个变量“x”,值为1。然后我们定义了一个匿名函数(一个lambda表达式)赋给类型Action。Action没有参数,没有返回值,但如果你观察“action”里的定义,你会发现它使用了“x”变量。这是变量是被action“捕获”或“携带”的,自动被添加到了action的运行环境中了。 当我们执行action时,它输出了我们预期的结果。请注意,当我们执行时,原始的“x”此时已经脱离了它当初的变量环境,但它仍然能用。 当你在代码调试器(debugger)里观察“action”时,会发现很有趣的事情。我们可以看到,C#编译器为我们创建了一个Target类,里面封装了x变量: 闭包(和higher order functions)都是非常有用的东西。如果你曾经开发过稍微复杂一点的Javascript程序,你可能就会知道,这个东西可以被当成很多面向对象特征的替代品,就像C#那样。前不久我还在C#里写了一个例子来验证这种想法。 习惯性的,John Skeet给了闭包一个更详细的描述。更详细的信息请查看《深入解析C#》里的这一章。里面还介绍了你在闭包里会经常会遇到的一些错误。 推荐阅读: C#与闭包 什么是闭包,我的理解