JavaScript闭包详解

30 阅读3分钟

JavaScript闭包

为什么要闭包

全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在,而在函数内部声明的变量,只在函数内部起作用。

这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。

导入计时器问题

如果使用全局变量

var counter = 0;
 
function add() {
   return counter += 1;
}
 
add();
add();
add();
 
// 计数器现在为 3

计数器数值在执行 add() 函数时发生变化

但问题来了,页面上的任何脚本都能改变计数器,即便没有调用add()函数**(counter会受到污染)**

如果使用局部变量

function add() {
    var counter = 0;
    return counter += 1;
}
 
add();
add();
add();
 
// 本意是想输出 3, 但事与愿违,输出的都是 1 !

以上代码将无法正确输出,每次我调用 add() 函数,计数器都会设置为 1

解决思路------JavaScript 内嵌函数

所有函数都能访问全局变量

实际上,在 JavaScript中,所有函数都能访问它们上一层的作用域

JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量

该实例中,内嵌函数 plus() 可以访问父函数的 counter 变量:

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

思考:

如果我们能在外部访问 plus() 函数,这样就能解决计数器的困境。

我们同样需要确保 counter = 0 只执行一次。

我们需要闭包。

解决方法------闭包

导读:

内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量。

外层作用域下无法获取内层作用域下的变量。

特性:

  1. 函数嵌套函数
  2. 函数内部可以引用函数外部的参数和变量
  3. 参数和变量不会被垃圾回收机制回收

解析:

function a (){
    var name = "dov";
    return function(){
        return name;
    }
}
var b =a();

在这段代码中,a()中的返回值是一个匿名函数,这个函数在a()作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回值赋给全局作用域下的变量b,实现了在全局变量下获取到局部变量中的变量的值

function fn(){
    var num =3;
    return function (){
        var n=0;
        console.log(++n);
        console.log(++num);
    }
}
var fn1=fn();
fn1()//1 4
fn1()//1 5

一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 ... },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题。

闭包的好处与坏处

好处:

  1. 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
  2. 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
  3. 匿名自执行函数可以减少内存消耗

坏处:

  1. 其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
  2. 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响