闭包之我的理解

2016-10-26  本文已影响19人  Mescal川

闭包是javascript的一个难点,也是一个重点,很多高级应用都需要用到闭包。在学习闭包的过程中看了不少书和大牛的博客,下面来说说我对闭包的理解和看法。

函数

要说闭包就不得不先说一下函数,这个javascript的中流砥柱。
众所周知在ES6之前js都没有class这个关键字,一切的面向对象都是通过function模拟的。

一个简单到不能再简单的例子:函数内部可以访问外部的变量(全局变量)。

var n=100;
function f1()
{
    console.log(n);
}
f1();//100

而外部却不能访问函数内的局部变量。

function f1()
{
    var n=100;
}
console.log(n);//n is not defined

那么问题来了,如果我想访问函数内的局部变量怎么办?一种解决办法就是使用闭包。
什么是闭包?我个人认为函数中的函数就是闭包,有点像java的内部类,既然函数可以访问外部变量,那么函数中的函数也可以访问外层函数的变量,这个内层函数就是闭包。

function f1()
{
    var n=100;
    function f2()
    {
        console.log(n);
    }
    f2();
}
f1();//100

上述写法比较麻烦,因此可以把f2作为一个返回值返回给f1。

function f1()
{
    var n=100;
    function f2()
    {
        console.log(n);
    }
    return f2;
}
f1()();//100
//相当于var f=f1();f();

下面来看一个问题:这里有5个li,名字是0到4,我希望我点击0的时候在控制台输出0,点击1的时候在控制台输出1,以此类推。

var lis=document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++)
{
    lis[i].onclick=function()
    {
        console.log(i);
    }
}

奇怪的事情发生了,无论我点击哪一个在控制台输出的都是5。
这是为什么呢?
仔细观察,我为每个li创建了一个匿名函数,匿名函数形成了闭包,它们都在引用外部的i,当i改变时,自然所有匿名函数里面的i都改变了。用几张图来说明。




当i增加到5的时候不再满足循环条件,由于五个匿名函数都是引用同一个i,所以它们打印出来的都是5。

既然它们是引用同一个i,那么只需为每一个i创建一个备份:

var lis=document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++)
{
    lis[i].onclick=function(num)
    {
        return function()
        {
            console.log(num);
        }
    }(i)
}

这里在匿名函数接收一个参数并且立即执行,匿名函数内部又创建了一个函数负责把这个参数返回给匿名函数,由于是按值传递的,所以相当于给i做了一个备份。就算i一直在改变,而每个匿名函数的num是固定的。

闭包中的this

在闭包中使用this会出现一些问题,在全局函数中this指向的是window,当函数以方法被调用时,this就指向这个对象。但是,匿名函数的执行环境具有全局性,this一般指向window。参考红宝石书中的一个经典的问题:

var name="The Window";
var object={
    name:"My Object",
    getNameFunc:function(){
        return function(){
           return this.name;
        };
    }
};
alert(object.getNameFunc()());//"The Window"

内部函数在搜索this的时候只会搜到其活动对象为止,由于匿名函数的执行环境具有全局性,因此当前的活动对象就是window。

可以通过改变当前的活动对象来改变this的指向:

var name="The Window";
var object={
    name:"My Object",
    getNameFunc:function(){
        var that=this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());//"My Object"

这里把this对象赋值给了一个that变量,因此当前的活动对象就从window变成了object。

闭包引发的问题

闭包虽好,但是也引发了一系列问题,尤其是内存泄漏。js的垃圾回收机制是引用计数的,当一个变量不再使用的时候(引用计数为0)垃圾回收机制就会把它清理掉。而闭包的存在会导致引用数至少为1。

function f()
{
   var myDiv=document.getElementById("Div1");
   myDiv.onclick=function()
   {
        console.log(myDiv.id);
   };
}

上述代码中,函数f需要等myDiv清除后才能被清除,而myDiv由于匿名函数的存在,它的引用数至少为1,因此它所占用的内存永远不会被回收。

解决办法:

function f()
{
   var myDiv=document.getElementById("Div1");
   var id=myDiv.id
   myDiv.onclick=function()
   {
        console.log(id);
   };
   myDiv=null;
}

把myDiv.id保存在一个变量中,并且在闭包中引用这个变量,这样就与myDiv没有关系了。但是闭包会引用包含函数的整个活动对象,因此必须把myDiv设为null来解除对它的引用,确保垃圾回收机制能够把资源回收。

使用闭包应该注意的问题:闭包虽然好,但是在实际应用中尽量减少 闭包的使用,并且为了防止内存泄漏,要时刻记得在闭包完后清除它的局部变量。

上一篇下一篇

猜你喜欢

热点阅读