闭包是JavaScript中一个重要的概念,闭包对于大部分的初学者来说理解起来十分的困难晦涩,也是面试中经常遇到的问题,今天呢就让我们好好的分析闭包

闭包的概念

  • • 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。——出自《JavaScript高级程序设计(第三版)》

  • • 函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。——出自《JavaScript权威指南(第六版)》

  • • 闭包是指一个函数可以记住其外部变量并可以访问这些变量。在某些编程语言中,这是不可能的,或者应该以一种特殊的方式编写函数来实现。但在 JavaScript 中,所有函数都是天生闭包的。——出自《现代JavaScript教程》

一句话总结:在一个函数内部创建另一个函数,内层函数中访问到其外层函数的变量,就会形成闭包

初步理解闭包的定义

显然大部分人都da不喜欢这种官方的解释,ok,那直接上代码。

function a () {
    var b = 1;
    return function () {
      b++;
      console.log(b);
    }
}

var c = a();
c();//b=2
c();//b=3

console(b) // undined

这是一个十分简单的关于闭包的例子,在函数a中返回了一个函数赋值个变量c执行c发现b累加了,再次执行发现b在原来的基础上又加了1,且外部访问不到b变量,这个例子就非常直观的说明了闭包的特点:

  • • 返回函数内部函数

  • • 形成闭包内的变量保存在内存中不被销毁

  • • 形成闭包的需要外部变量对函数内部函数的引用

闭包的用途

当我们重复使用一个变量名时,会考虑到命名污染的问题,这时候就可以使用闭包。这种变量被叫做私有变量或者局部变量。

for(var i = 0; i <= 10; i++){

  setTimeout(function (){

  console.log(i)

  },i)

}

每次循环,我们都会挑出一份i用来输出,但因为setTimeout会在循环完成后执行,每次的i都在同一全局作用域下,于是后来居上,覆盖了前面的i,再由setTimeout执行时,就全是11了。

怎么使得每次循环输出正确呢?

我们只需要将每次的i变成一个私有变量,有独立的作用域,让其不再篡改就OK了。

这个时候我们就需要使用到IIFE( 立即调用函数表达式),这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。第一部分是包围在 圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。

第二部分再一次使用 () 创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。

for(var i = 1; i <= 5; i++) {

    (function (j) {

        setTimeout(function () {

        console.log(j)

    }, j* 1000)

    })(i);

}

这里利用IIFE拥有独立的词法作用域的特性,将变量私有化,这样在setTimeout执行时就会得到正确输出。没错,好像就是利用闭包将每次的变量缓存起来,放在独立的内存中。

总结

闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。闭包通常会跟很多东西混搭起来,接触多了才能加深理解。