在不知道闭包之前,因为链式作用域的关系(只能向上一级的作用域查找变量),我一直认为函数内的变量在函数之外是无法访问到的。直到认识了你--闭包,让我彻底颠覆了之前的想法,也让我明白了不要随便下结论,要不你会死的很惨……
谈到闭包或许会让你想到匿名函数,因为这两个神奇的小怪物会常常让人混淆。闭包呢是指有权访问另一个函数作用域中的变量的函数,我们创建闭包最常用的方法就是在一个函数内部创建另一个函数,以下面为例就是一个最简单的闭包:

function f(){
    var num=10;
    function f1(){
        return num;
    }
    return f1;
}
var result=f(); 
console.log(result());  //10

1、作用域

谈论闭包之前呢还是需要想回顾一下变量的作用域问题。
作用域无非就是全局变量和局部变量。
这样有引出了作用域链,在作用域链只能向上一级查找变量,所以说在函数内部我们可以直接读取全局变量,但在函数外部是无法读取函数内部的局部变量的。
我们都知道函数的执行依赖于变量作用域,这个作用域是在函数定义时候决定的,而不是在函数调用时决定的。看下面例子:

var scope="global scope";
function checkscope(){
    var scope="local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();    //local scope

嵌套的函数f()是定义在函数内部的,其中的变量scope是局部变量,不管在何时何地执行f(),作用域链是不会改变的。

var num1=10;
function f1(){
    var num2=20;
    num3=30;
    alert(num1);
}
f1();  //10
alert(num1);  //10
alert(num2);  //num2 is not defined
alert(num3);  //30

这里有一个需要注意的地方,函数内部声明变量一定要使用var,如果一不小心忘记了,那对不起了,你将声明一个全局变量!

2、如何在函数外部读取到函数内部的变量呢?

但是在某种情况下因为一些原因我们是需要得到函数内部的局部变量,但是前面我们也尝试过了,在函数外部是无法得到内部的变量的,怎么办,这可愁死宝宝了……别急,办法还是有的:

function f(){
    var num=10;
    function f1(){
        return num++;
    }
    return f1;
}
var count=f();
count();  //10
count();  //11
count();  //12
count();  //13
count();  //15

正如刚开始我们的例子,f1被包含在了f内部,此时f的变量对于f1都是所谓的上一级,也就是都是可见的,随时都可以访问。既然f1可以读取f中的局部变量,那我们只需把f1作为返回值即可在f外部取得f的局部变量了。

3、到底什么是闭包?

其实上一段代码中的f1函数就是我们说的闭包。其实闭包的理解很简单,闭包就是能够取得函数内部变量的函数。
在本质上闭包其实就是链接函数内部与函数外部的桥梁。

4、闭包的用途

说了好多,但是闭包到底有什么用,为什么要学习这个“违法”的怪物?闭包的用处实在是太多了,最最重要的就是:可以在函数外部读取函数的变量,另一个就是让这些变量的值永远保存在内存中。what?莫急,请看下面代码:

function f(){
    var num1=99;
    add=function(){
        num1+=1;
    }
    function f1(){
        return num1;
    }
    return f1;
}
var result=f();
alert(result());  //99
add();
alert(result());  //100

为什么num1没有在result()调用之后被清楚呢?原因就是f是f1的父函数,f1最后是被赋值给了全局变量result,这导致了f1会一直在内存当中,然而f1的存在是依赖于f的,因此f也将一直在内存中。
此处有玄机哦,注意add函数前面没有var关键字,也就是说add现在是一个全局变量,而add的值是一个匿名函数(另一个小怪物),而这个匿名函数本身也是一个闭包(因为他访问了局部变量num1),他可以在函数外部对函数内部的变量进行操作。

5、闭包里的this

我们都知道,this对象是在运行时基于函数的执行环境绑定的:全局作用域下,this就是window,而当函数作为某个对象的方法调用时,this等于那个对象(其实在全局作用于下,函数就是window的方法)。不过匿名函数的执行环境是具有全局性的,因此this通常指向window。

var name="the window";
var object={
    name:"my object",
    getNameFunc(){
        return function(){
            return this.name;
        }
    }
}
alert(object.getNameFunc()());  //"the window"

但不全是:

var name="the window";
var object={
    name:"my object",
    getNameFunc:function(){
        var that=this;
        return function(){
            return that.name;
        };
    }
}
alert(object.getNameFunc()());  //"my object"

6、使用闭包要小心

1)我们上面提到过闭包可以让变量永久保存在内存中,这会导致内存消耗过大,所以使用闭包要谨慎,否则会带来性能问题,IE中会导致内存泄漏。

function assigmHandler(){
    var element=document.getElementById("myelement");
    element.onclick=function(){
        alert(element.id);
    }
}

此后element将永远驻留在内存中。解决办法就是在退出函数之前把不是用的局部变量删除。

function assigmHandler(){
    var element=document.getElementById("myelement");
    var id=element.id;
    element.onclick=function(){
        alert(id);
    }
    element=null;
}

2)闭包会在父函数外部改变父函数的变量,比如上面的例子:

function outer() {
    var obj = {
        name: 'xiaoming'
    }
    return {
        number: obj,
        getObj: function () {
            console.log(obj)
        }
    }
}
var people = outer();
people.getObj();   //Object {name: "xiaoming"} 
people.number.name = 'xiaozhang';
people.getObj();  //Object {name: "xiaozhang"}

如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。


小白兔zqd
22 声望0 粉丝