闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来
- 没看懂?我也是。不过我找到一种更加通俗但是可能不太全面的说法:闭包就是能够读取其他函数内部变量的函数,也就是说,我们可以简单的理解为:“++闭包是定义在一个函数内部的函数++”
- 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
- 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
众所周知,JavaScript中内部函数可以引用其父元素的参数及局部变量。由此我们衍生出了map()函数用来接收函数作为自身参数。然而还有另一种更加好用的方法—-闭包
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外
对于map()函数来说,只能返回结果。但是若暂时不需要结果,我们就可以利用高阶函数的特性:返回函数(闭包亦是高阶函数的一种)
上一篇博客我们提到,js中函数可以作为返回值。闭包正是利用了这一点。由于返回的函数在其内部引用了局部变量arr,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。所以闭包看起来简单,实现起来可不简单。
闲话不说我们先看代码
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
f1(); // 16
f2(); // 16
f3(); // 16
注意:返回的函数并没有立刻执行,而是直到调用了f()才执行
咳咳,是不是对结果非常疑惑?学习中我也有此类困惑。事实上其特性是这样的:返回的函数引用了变量i,但其并非立即执行,而是++等到三个函数都返回时++,他们引用的变量i已经变成了4,因此最终结果变成了16。所以长点记性,千万!千万不要放在返回函数中引用任何循环变量
当然,循环作为代码重要的组成部分想省略是不太现实的,这时就需要我们换种方法:再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变
因此,上边的代码我们可以这样实现避免出现三个16的诡异结果
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
此处分享干货:“创建一个匿名函数并立刻执行”语法
(function (x) {
return x * x;
})(3); // 9
不过,该方法还需要注意:必须将整个函数定义括起来,否则会报SyntaxError错误
通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
(function (x) {
return x * x;
})(3);
- 方才给大家展示了js中闭包的一个功能:返回一个函数并延迟执行
- 在面向对象的语言中有private用来修饰成员变量,而在只有函数没有class机机制的js中,我们同样可以利用闭包封装私有变量
- eg:用js创建一个计数器
'use strict';
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
它用起来像这样:
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
- 同时,闭包还有一个作用:将多参数函数变成单参数的函数。我们来举几个例子
- 将函数x^y(Math.pow(x, y))计算公式改为x^2 或 x^3,函数名为pow2,pow3
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);
pow2(5); // 25
pow3(7); // 343
- 说了这么多,我们来详细讨论一下闭包的用途吧
- 读取函数内部的变量
- 让函数内部的变量的值始终保持在内存中
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除
疑惑?咱们得详细说一下:f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
下面我们进行一下系统讲解
调用result时也相当于调用了f1
但是外部父函数无法访问子函数的变量
所以nAdd+1的作用也相当于没有发挥出来,最后return f2的时候还是返回了999
而在外部调用了一次nAdd之后n的值更新,而因为闭包可以让变量的初始值始终保持在内存中,所以n直接更新为1000
然后进到函数内输出
- 最后我们再总结一下使用闭包的注意项吧
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
- 最后我们放两段代码来加深大家的理解。(据说理解这俩闭包就理解的差不多了)
-
- eg1
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
-
- eg2
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
最后,声明本篇博客后半部分是复制粘贴了网上某大神的博客,具体出处已不得而知,但还是表示由衷的感谢。关于闭包在面试中仍是一大考点,鉴于个人水平有限,已经从网上摘录了几篇不错的文章于我的收藏之中,方便查看
9种办法解决JS闭包经典面试题之for循环取i:http://blog.csdn.net/u013243347/article/details/52134643
JS闭包导致循环给按钮添加事件时总是执行最后一个:http://blog.csdn.net/xiaozji/article/details/43530563
for循环中的闭包问题及解决方案:http://blog.csdn.net/qq_34986769/article/details/52144307
js 解决js for 循环中的闭包问题:http://blog.csdn.net/z_572712675/article/details/71274315
for循环里面嵌套if的问题的问题:http://ask.csdn.net/questions/202388
JS之经典for循环闭包问题解决方法:http://blog.csdn.net/yuli_zoe/article/details/43305855