JS基础--闭包

JS基础–闭包

官方解释: 所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

简单的说,闭包形成的条件,就是函数返回函数。

function a(){   
  var i=0;   
  function b(){   
    alert(++i);   
  }   
  return b;   
} 

执行了a()()就是一个闭包。一般闭包就是一个函数,闭包还可以是下面的这种形式。

(function (){ 
  var i=0; 
  function b(){ 
    alert(++i); 
  } 
  return b; 
})()();

执行上面的代码,就会了解闭包是一个拥有环境的表达式。这里对于环境的理解是最重要的。

谈到闭包就必须要了解js的作用域和变量提升的概念。

变量作用域:

首先js不同java和C这类的语言,js是没有块级作用域的(es6之前)。js是函数作用域。所谓的函数作用域就是在一个函数内不管哪里定义的变量都可以在该函数内访问。函数里是可以访问函数外的变量,但是函数外就不能访问函数里的显示定义变量。自然,全局变量就不在这个范围考虑了。

变量提升:

如果深刻理解了变量作用域,那么变量提升也很简单了。js可以在第一行访问最后一行定义的变量,访问的只是声明的变量,赋值还是要按照代码一行一行执行。为什么js可以在一个函数里任何地方访问任何地方的变量(这里的任何地方排除函数里的函数),那是因为js函数作用域内把var声明的变量都放在了执行语句前面。下面两个例子来阐释变量提升。

function(){
  alert(a);//弹出undefind
  var a = 1;
}

其实变量提升的本质是:

function(){
  var a;
  alert(a);//弹出undefind
  a = 1;
}//这样是不是看的更加明白了。

但是这里变量提升只是打个比喻,好让大家明白js的函数作用域。其实实质不是这样的,实质是每个函数都有个内存区域来存储变量名。也就是一个存储变量名的环境。在函数执行之前就已经把环境创建出来了。函数中如果要用到一个变量,那么就会先到变量环境中查找,如果查找不到就往上查找,最后一直找到本地环境中。存储变量名的内存区域其实就是作用域里的最核心概念了。

到了这里是否理解了闭包是一个拥有环境的表达式这句话。这时候剩下的形容词,许多变量是不是很容易理解。

我们顺着来理解一遍,闭包就是一个拥有了环境绑定了许多变量的表达式。这样是不是很容易理解了。

到这里还不算了解完闭包,知道了闭包是什么就要知道闭包是干嘛用的。

闭包的作用:就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。

很典型的一个例子:

function a(){
    var arr = [];
    for(var i=0;i<5;i++){
        arr[i] = (function(n){
            return function(){
                return n;
            }  
        })(i);
    }
    return arr;
}
// 把函数a的执行结果(返回的arr)赋值给b;
var b = a();
console.log(b[0]()); // 0
console.log(b[1]()); // 1
console.log(b[2]()); // 2
console.log(b[3]()); // 3
console.log(b[4]()); // 4

执行过程:在每一次循环中都会立即执行一个匿名函数把 i 当前的值赋值给匿名函数的局部变量 n (变量是值传递)并返回一个匿名函数(地址)赋值给数组。由于我们在匿名函数里返回了变量 n ,所以在 for 循环结束后,每一个匿名函数执行时对应的变量 n 是不会被垃圾回收机制回收,一直存在内存中。那为什么这里的 n 可以实现不同的值,立即执行一个匿名函数并返回一个匿名函数(地址)赋值给数组,所以每一个匿名函数都会有一个属于自己的局部变量 n 。也许你会说那这些匿名函数不是一样的吗,没错,可你别忘了函数也是一个对象,对象是址传递的,虽然它们长得是一样,但它们在内存中的实际地址是不一样的,这也是为什么可以有多个 n 值存在的原因。

闭包的应用场景:

  • 保护闭包里的变量安全。因为只有闭包里的函数才能访问闭包里的变量,所以外界就不能直接对闭包里的变量进行操作。
  • 在内存中维持一个变量。单例模式是其中的经典模式。