本文目录一览:
闭包是什么,有什么特性,对页面有什么影响
闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。在 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中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段代码:
function a() {
var i = 0;
function b() {
alert(++i);
}
return b;
}
var c = a();
c();
特点
这段代码有两个特点:
- 函数
b
嵌套在函数a
内部; - 函数
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);
创建一个块
块使用脱字符(^)作为起始标志,使用分号作为结束标志。下面的例子声明一个简单块,并且将其赋给之前声明的block变量(oneFrom)。
int (^oneFrom)(int);
oneFrom = ^(int anInt) {
return anInt - 1;
};
微观世界
如果要更加深入的了解闭包以及函数a
和嵌套函数b
的关系,我们需要引入另外几个概念:函数的执行环境(execution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a
从定义到执行的过程为例阐述这几个概念。
- 当定义函数
a
的时候,js解释器会将函数a
的作用域链(scope chain)设置为定义a
时a
所在的“环境”,如果a
是一个全局函数,则scope chain中只有window对象。 - 当函数
a
执行的时候,a
会进入相应的执行环境(execution context)。 - 在创建执行环境的过程中,首先会为
a
添加一个scope属性,即a
的作用域,其值就为第1步中的scope chain。即a.scope=a
的作用域链。 - 然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到
a
的作用域链的最顶端。此时a
的作用域链包含了两个对象:a
的活动对象和window对象。 - 下一步是在活动对象上添加一个arguments属性,它保存着调用函数
a
时所传递的参数。 - 最后把所有函数
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中的变量查找机制。
应用场景
- 保护函数内的变量安全。以最开始的例子为例,函数
a
中i
只有函数b
才能访问,而无法通过其他途径访问到,因此保护了i
的安全性。 - 在内存中维持一个变量。依然如前例,由于闭包,函数
a
中i
的一直存在于内存中,因此每次执行c()
,都会给i
自加1。 以上两点是闭包最基本的应用场景,很多经典案例都源于此。
回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第三者所引用,那么这两个互相引用的对象也会被回收。因为函数a
被b
引用,b
又被a
外的c
引用,这就是为什么函数a
执行后不会被回收的原因。
匿名内部
在Python中的闭包(Closure)
学过Java GUI编程的人都知道定义匿名内部类是注册监听等处理的简洁有效手段,闭包的定义方式有点类似于这种匿名内部类,但是闭包的作用威力远远超过匿名内部类,这也是很多流行动态语言选择闭包的原因,相信你在JavaScript中已经了解它的神奇功效了。
定义
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
简单闭包的例子:
下面是一个使用闭包简单的例子,模拟一个计数器,通过将整型包裹为一个列表的单一元素来模拟使看起来更易变:
函数counter()
所作的唯一一件事就是接受一个初始化的值来计数,并将该值赋给列表count
成员,然后定义一个内部函数incr()
。通过内部函数使用变量count
,就创建了一个闭包。最魔法的地方是counter()
函数返回一个incr()
,一个可以调用的函数对象。
运行:
c = counter(5)
type(c)
# type 'function'
print(c()) # 6
print(c()) # 7
代码格式较重要:
c2 = counter(99)
# 100
print(c()) # 8
java中的闭包什么意思,有什么作用,麻烦高手给解释下,感谢!
- 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引用了外部函数中的变量。
- 是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
- 是一个匿名的代码块,可以接受参数,并返回一个返回值,也可以引用和使用在它周围的,可见域中定义的变量。
- 是一个表达式,它具有自由变量及邦定这些变量的上下文环境。
- 闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。
- 是指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
- 闭包是可以包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。
简单的闭包问题?
概念
闭包,在《javascripts高级程序设计》里面是这样介绍的:闭包是指有权访问另一个作用域中的变量的函数。额。。这句话我以前看过很多遍,但依然不是很懂,只知道它是跟作用域有关。现在我知道了,如果这句话换成:但凡是内部的函数被保存到了外部,必定生成闭包。这样就容易理解多了不是。 我们以下面的这个代码块为例:
例子解释
function a() {
const num = 100;
function b() {
num++;
console.log(num);
}
return b;
}
const demo = a();
demo();
demo();
我们先执行上述代码,看看结果是什么:
为什么是这样呢?
a()
执行的结果是返回b
,所以demo
指的是b
,则demo()
指的是b()
;也就是说原先在a
里面的b
,在b
的外部执行;
我们已经知道了作用域和作用域链的概念,上面代码的作用域是这样的:
a
执行完,返回了b
,此时的b
只是声明,但还没调用,所以没有形成自己的AO,但作用域链和 a
doing 时是一样的,所以虽然 a()
的作用域被销毁了,但是相同的一份却被b
保存到了外面。这也就是内部函数被保存到了外面形成闭包的本质。这样也不难理解为什么上述代码打印出来的值是那样的了:
执行第一个demo()
时,也即是执行 b()
,由于b
保存了a
的作用域链,所以也可以访问到 num
,执行 b()
后,加一;
那为什么第二次执行 demo()
,打印出的值还是有自增了呢?这是因为操作的都是保存在 b
里的 num
,虽然每次调用 demo()
都会形成新的作用域链,但是num
,却是每次从上一次的作用域链直接 copy到当前作用域链中的。
这样形成的闭包虽然可以使外部可以访问到内部的函数,但是导致了原有的作用域链不释放,会造成内存泄漏。(内存泄漏的意思就是占用内存,可用内存资源变少了)。所以如果不是特殊需要,应尽量防止这种情况发生。
并且,作用域链的配置机制引出了一个值得注意的副作用:即闭包只取得包含函数中任何变量最后一个值,比如下面这个例子:
function createFunctions() {
var result = [];
for(let i = 0; i < 10; i++) {
result[i] = function() {
console.log(i);
}
}
return result;
}
var myArr = createFunctions();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
这个函数会返回一个函数数组,表面上看,似乎每个函数都应该有自己的索引值,即会返回:0,1,2...9;但实际上,每个函数都会返回10;这是因为在createFunctions()
执行时,for循环跳出的条件是i=10
;所以函数返回后,i
的值是10;而每个result
的作用域链中都保存这createFunctions()
的AO,所以他们引用的都是createFunctions()
的i
值,所以每个函数内部i
的值都是10;
这样,我们可以创建一个立即执行函数强制让闭包的行为符合预期:
function createFunctions() {
var result = [];
for(var i = 0; i < 10; i++) {
(function(j) {
result[i] = function() {
document.write(j + " ");
}
})(i);
}
return result;
}
var myArr = createFunctions();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
典型应用
下面看看几个用到闭包的典型例子:
实现共有变量
如累加器:调用多少次,累加多少次,用闭包更加模块化
function add() {
var count = 0;
function demo() {
count++;
console.log(count);
}
return demo;
}
var counter = add();
counter(); // 1
counter(); // 2
counter(); // 3
实现缓存
如eater:eat
和push
保存的都是eater
的AO;所以eat
中food
改变后,实际上是eater
变了,所以也会影响push
;
function eater() {
var food = '';
var obj = {
eat: function() {
console.log('eating' + food);
food = '';
},
push: function(myFood) {
food = myFood;
}
}
return obj; // 相当于返回里面的eat和push操作food;
}
var eater1 = eater();
eater1.push('banana');
eater1.eat();