一、闭包的概念

闭包官方给出的解释:闭包是函数和声明该函数的词法环境的组合。

下面我们举一个经典的且最简单的闭包demo

function closure(){     var a=4;     function clo(){         console.log(a)     }     clo();  } closure();

从上面的例子中,我们可以看出闭包需要的三基本个条件是:1.一个外层函数。2.一个内部函数。3.局部变量。

二、闭包的用途

要理解闭包的用途首先要理解javascript的特殊的变量作用域。

变量的作用域只有两种:全部变量和局部变量。而javascript语言中,函数内部可以直接读取全局变量,但是函数外部无法读取函数内部的局部变量。

而我们需要用到的闭包的场景就是:我们需要一个外部不可以直接访问一个函数内部变量的的环境。因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。比如:插件封装,面向对象编程等。

闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

1.DOM中使用闭包实例

<a href="#" id="size-12">12</a><a href="#" id="size-14">14</a><a href="#" id="size-16">16</a>function makeSizer(size) {  return function() {    document.body.style.fontSize = size + 'px';  };}var size12 = makeSizer(12);var size14 = makeSizer(14);var size16 = makeSizer(16);document.getElementById('size-12').onclick = size12;document.getElementById('size-14').onclick = size14;document.getElementById('size-16').onclick = size16;

2.用闭包模拟私有变量

 function closure2(){    var pCounter=0;    function changeBy(val){      pCounter += val;    }    return{        add:function(){            changeBy(1)        },        reduce:function(){            changeBy(-1)        },        value:function(){           return pCounter        }    } } var counter1= closure2(); var counter2 = closure2(); console.log(Counter1.value()); Counter1.add(); Counter1.add(); console.log(Counter1.value()); /* logs 2 */ Counter1.reduce(); console.log(Counter1.value()); /* logs 1 */ console.log(Counter2.value()); /* logs 0 */

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

三、闭包的缺点

1.缺点一:变量发生变化

   function arr(){           var res=new Array();           for (var i = 0; i< 10; i++){              res[i]=function(){                  console.log(i);              }           }           return res;       }      var arr1=arr();      arr1[0]();//10      arr1[1]();//10

因为闭包里面的函数调用发生在for循环结束之后,此时变量i的值是10,且res组成的闭包集合共用一个词法环境里面的变量i,因此每个闭包所输出的值都是10。

解决此类情况的方式:
a.使用立即执行函数

   function arr(){        var res=new Array();        for (var i = 0; i< 10; i++){            res[i]=(function (num) {                return function(){                    console.log(num)                }            })(i)        }        return res;      }      var arr1=arr();      arr1[0]();//0      arr1[1]();//1

b.使用es6中的let

function arr(){     var res=new Array();     for (let i = 0; i< 10; i++){        res[i]=function () {                console.log(i)        }     }     return res;      }    var arr1=arr();      arr1[0]();//0      arr1[1]();//1

这两种方式的共性就是把变量i,变成了每个函数的局部变量,因此在执行闭包的时候,局部变量i不会发生变化。

2.缺点二:this指向问题

   var obj = {     name:"this",      getName:function() {         return function () {             console.log(this.name)         }     }     }     var arr= obj.getName();     arr()//undefined

this指向当前调用函数的上下文,arr函数创建在全局环境中,所以在调用arr时,在非严格模式的浏览器环境中this指向window,在严格模式下this是undefined,而这两种情况下都没有一个全局的name,所以函数的执行结果为undefined。

3.缺点三:内存泄漏

function  showId() {    var el = document.getElementById("div1")    el.onclick = function(){      console.log(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放    }}// 改成下面function  showId() {    var el = document.getElementById("app")    var id  = el.id    el.onclick = function(){      console.log(id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放    }    el = null    // 主动释放el}

当闭包中引用了全局变量时,记得在引用完之后进行释放,避免造成内存泄漏。