JavaScript闭包
闭包是javascript(以下简称js)中比较难以理解的重要知识点之一,能否理解闭包甚至可以决定你是否能够写出高级的js应用,例如OOP,设计模式,造轮子等等。
各种文献和百科中对闭包的概念介绍的基本都特别晦涩难懂,那到底什么是闭包,又应该如何理解?
想要弄懂闭包,首先要对js的作用域有了解。
作用域链
我们都知道,js的作用域有两种:全局作用域、局部作用域。
由于js语言的变量声明方式的特殊性(在ES6之前是没有常量和局部变量的),我们只能使用 var 关键字和直接声明的方式来声明变量,那如何判断变量是在局部作用域还是在全局作用域就是作用域需要做的事情,而沿着链条向上查找变量即是作用域链。
我们来看下面的代码:
var str = "This is Window";
function foo(){
alert(str);
var str1 = "This is Fun";
}
foo(); //This is Window
alert(str1); //undefined
上例中我们可以看到,在局部作用域中,foo是可以访问到全局作用域的,而在全局作用域中的alert却访问不到foo中的局部变量,这就是js中的作用域。
在函数创建时,即创建了作用域链,这个作用域链会链接本身与父级作用域,形成一个链条,在需要使用变量时,会先查找自身作用域,如果没有则会沿着链条向上查找,直到全局作用域,如果没有则会返回undefined。因为作用域链条是止于自身的,所以自身的子级作用域是访问不到的。
需要注意的是,在函数内部如果声明局部变量,必须使用 var 关键字,虽然直接写变量是可行的,但是相当于创建了一个全局变量,而不是在此局部作用域中的局部变量。
闭包
弄懂了作用域链,就可以更好的了解闭包了。
我们先来看一段百科中对于闭包的描述
闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
好吧,我知道确实晦涩难懂,而且我也觉得这根本就不是给人看的。。
其实闭包在我理解看来,就是是我们可以在外部访问内部变量的函数。
我们可以看一下下面的例子
function foo(){
var fName = "This is Fn";
function fon(){
alert(fName);
}
return fon();
}
var test = foo();
test();
在上面的例子中,fon就是闭包函数,我们在foo中声明了变量,进而在fon中可以访问变量fName。
此时我们将fon返回给foo即可以将fName给外部函数调用。
同理,如果我们只是想调用其内部的变量,而不是alert出来也可以这样写:
function f1(){
var a = "a";
function f2(){
var b;
return b = a;
}
return f2();
}
alert("我是f1中的变量" + f1()); //我是f1中的变量a;
只要将f2当作高级函数传给全局变量,此时既能调用f1中的变量了。
闭包除了可以使我们访问内部的变量,还有另一个用处,即将变量长时间保存在内存内不被清除。
我们都知道js中有一个机制,即垃圾回收(Garbage Collection)机制。
js中的垃圾回收机制有两种,这也是早期浏览器大战的遗留问题,不过无论哪种,都不会清除闭包函数。
具体原因是因为闭包虽然已经被使用完毕,但是由于其仍然被外部函数所引用,所以垃圾回收机制(不论是计数清除还是标记清除)。
所以我们可以看到下面的例子所输出的情况与平时不用闭包的普通情况所输出的不一样的地方:
function fun1(){
var n=999;
numAdd = function(){n += 1};
function fun2(){
alert(fun2);
}
return fun2;
}
var addFun = fun1();
addFun(); //999;
numAdd();
addFun(); //1000;
在这个例子中,第一次运行完addFun之后,n并没有被清除,因为其本身还在被外部引用。
所以在运行内部的numAdd之后,再次运行还会根据之前的值去继续运行。
后记:
其实闭包说简单简单,说难也很难,主要是看每个人的思维怎么样,绕过来弯的话就很容易理解了。
闭包是所有前端程序员所必需掌握的一个知识点,也是前端进步的路上不可不过的鸿沟。
希望这篇博客能对你学习闭包有一定的帮助。