举个栗子:
// 例1
var n = 999;
function f1(){
alert(n);
}
f1(); // 999
// 例2
function f2(){
var n = 999;
}
alert(n); // error
// 例3 实现闭包
function f3(){
var n = 999;
function f4(){
alert(n);
}
return f4;
}
var result = f3();
result(); // 999
- Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
- 另一方面,在函数外部自然无法读取函数内的局部变量。(如例2)
- 例3中。f4() 就是一个简单的闭包。在 f4() 中可以读取 n 的值。并且在外部可以调用
注:函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
再举个栗子:
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,可以在函数外部对函数内部的局部变量进行操作。
看到以上的写法,不难发现闭包的实现方式有很多 :
1、在函数内通过return返回一个函数
2、在函数内声明一个全局的匿名函数 (如nAdd)
闭包的概念就介绍完了。还有一些小小的细节问题:
并不是,不要混淆了闭包和匿名函数的概念
匿名函数
:顾名思义其实就是不写方法名的函数,通常声明了匿名函数就会立刻调用该函数。所以也不需要名称,类似这种:
(function(){})();
回到正题,上面的例子的匿名函数,赋值给了 nAdd
。所以匿名函数也有了一个名称:nAdd
。那就可以作用域范围内,调用 nAdd
这个方法了,并且例子中的 nAdd
并没有用 var
进行声明,所以nAdd
的作用域在window
,也就是全局函数了。
例子中的匿名函数在 f1
函数内。所以可以访问 f1
的属性和方法。而且能在外部调用,从而实现了在外部读取 f1
的属性和方法,也就符合我们闭包的概念: 闭包就是能够读取其他函数内部变量的函数
在说作用域范围之前,还是来个栗子
// 例1
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
// 例2
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
// 例3
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc().apply(object));
答案分为别: The Window
, My Object
, My Object
如果全对了。那恭喜已经了解了闭包的作用域和运行机制,如果一个都没对,那就… emmm…
其实闭包的作用域 就是
window
。而不是闭包所在的对象
在 闭包的作用
这一点中已经体现了。闭包调用后的变量并没有立刻被消除,而是保存了下来。
在例3中。我们用了 apply
修改了this的指向,那自然打印的又变成了 My Object
至此,闭包介绍就结束了。可以回顾一下还记得多少
最后附上几个经典的闭包栗子
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];
f1(); // 16
f2(); // 16
f3(); // 16
function count() {
var arr = [];
for (let 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];
f1(); // 1
f2(); // 4
f3(); // 9
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];
f1(); // 1
f2(); // 4
f3(); // 9
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function (n) {
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
这一部分栗子中,可能不完全设计闭包,主要还是 var
和 let
对变量和对程序的影响。如何使用闭包消除这些影响。
其实打印一下 f1
f2
f3
大概都清楚了
才疏学浅,如果有写错的地方请指出
本文参考了2个大佬的博客:
廖雪峰 闭包
阮一峰 学习Javascript闭包(Closure)