深入理解JavaScript系列:试着谈谈闭包

时间:2023-03-08 18:28:46

  闭包可能是JavaScript里最被人神乎其神的一个概念,世间万物皆凡夫俗子,你觉着他神奇是因为你根本没有了解,所有的事物当你了解透彻后就不会有这种不明觉厉的错觉了。哈哈哈,上来又是一顿哲学普及。

  下面开始进行阐述。

  1.什么是闭包?

  参阅了很多资料,最后比较靠谱的解释就是“闭包是一个函数”。

  闭包是一个函数,什么样的函数才能叫做闭包?可以从他的作用上理解闭包就是能够读取其他函数内部变量的函数。

  2.闭包是怎么形成的?

  首先再回顾下JavaScript的作用域这个概念。JavaScript是函数作用域而不是块状作用域,也就是说JavaScript的作用域是以函数来划分的,函数内部的变量为私有变量,而相对的全局变量是指在所有的函数外部定义的变量。全局变量自动归为全局对象global的属性,在web浏览器中这个global对象由window对象来承担这个全局变量的角色。

  一般的,在函数内部声明的加了var声明符的(函数内部定义变量不加var的话会默认声明为全局变量,为了避免全局空间变量污染所以要谨记)变量,在这个函数执行时候创建该函数相关的作用域,函数当然会有嵌套,所以就会存在作用域链接作用域的问题,也就是所说的作用域链。这个作用域链从最里层函数作用域开始,逐渐向外层链接,一层一层直到最外层的全局作用域。所以在内层的函数使用某个变量的时候会按照这个链接顺序去寻找,如果中途找到则停止,直到全局作用域还没有找到则该变量为未定义。

  正常情况,一个函数在调用开始执行时创建这个函数执行上下文及相应的作用域链,在函数执行结束后释放函数执行上下文及相应作用域链所占的空间。

  明白了作用域后再来看闭包怎样才能读取到其它函数的内部变量这个问题。首先,如果是函数A嵌套函数B,函数B自然可以访问函数A里的变量,向上还可以直到全局作用域的变量。这种情况肯定形成不了闭包。所以闭包是在某函数A之外的函数B想要访问函数A内部的变量下形成的。

  3.闭包怎么样才能读取到其它函数的内部变量

  最常见的形式就是在函数内部返回一个函数,这个返回的函数有引用外部函数的变量。示例代码如下:

function foo() {
var tmp = 0;
return function () {
console.log(tmp = tmp + 1);
}
}
var bar = foo(); // foo()执行后返回内部匿名函数,所以bar现在就是内部的那个函数的引用
bar(); //现在执行bar,也就是执行内部的那个匿名函数,输出为1,
bar(); //紧接着再执行bar,输出为2,也就是说foo函数内部的tmp变量不仅被外部的函数访问到了,而且tmp的值没有被清空,会累加

  这就满足了形成闭包的条件,即相对函数A(这里指foo())而言他外部的函数B(这里指bar(),其实bar只是内部匿名函数的引用在外部执行)访问到了A作用域的变量tmp,而且值的注意的是tmp变量一直存在于内存中。

  依据上边这段代码解释为什么变量会一直存在于内存中而不是随着函数执行完毕后就被垃圾回收。

  首先,bar是在外部作用域中,大部分时候是在全局作用域中,而全局作用域中的变量是一直存在于内存中的,所以当bar引用foo的时候,间接地foo也会一直存在于内存中,既然foo都一直存在于内存中,那foo所形成的函数作用域里的一切都会一直存在于内存中,因此就会出现上边看到的访问到的内部变量不会随着函数执行完后进行垃圾回收的情况。

  4.怎样才能理解闭包?

  首先明白他是一个能访问其它函数内部作用域变量的函数,打破了常规的函数作用域的限制,再理解就是他引用的变量会一直存在于内存中。

  下面贴一些闭包的代码,都是从其他地方看到的:

var object = {
a: 1,
getA: function () {
console.log(this.a=this.a+1);
}
}
object.getA();//输出为2
object.getA();//输出为3

  

var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();

  5.闭包都有什么用处? 

  1)一个是前面提到的可以读取函数内部的变量
  2)另一个就是让这些变量的值始终保持在内存中。
  6.使用闭包需要注意的地方有哪些?
  1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
  完!祝好运。