闭包及其应用

概念

javascript高级程序设计对它的定义是:能够获取其他函数作用域内变量的函数。换句话说就是定义在其他函数内部的函数。

我们来看一个经典的闭包例子,也是面试时经常会遇到的。

function createFunction() {
    var result = new Array();

    for(var i=0; i < 10; i++){
        result[i] = function() {
            return i;
        }
    }

    return result;
}
var resultFun = createFunction();
复制代码

面试官会问你,resultFun[6]()的结果是神马?大家都知道的,是10。面试官会接着问你,为什么是10,答是闭包。为什么闭包会造成这种结果,....;可能有的小伙伴就会懵逼了。下面我们就来解释一下为什么会这样。

首先我们想一下,resultFun[6]这个函数中的变量i,它是指向谁的。这里就涉及到了作用域链的概念,每个函数都有一个作用域链,这个作用域链表示的是这个函数能够访问哪些变量。一个函数初始化的时候,它的作用域链会被创建,并被挂载到global对象的[[Scope]]属性上,这个属性是一个内部属性,我们访问不了。作用域链其实是一个数组,这个数组从前往后依次表示的是这个函数能够访问的环境或者说外层函数。拿result[i]举例,它初始化的时候,作用域链中有两个对象,一个是createFunction的变量对象,一个是全局变量对象。createFunction的变量对象是所有的可在createFunction内可以访问的变量,包括函数参数、内部变量、内部函数。全局变量对象就不用说了,所有函数的作用域链顶端都是全局变量对象。当函数执行的时候,该函数内部可访问的变量会变成一个变量对象,也被叫做活动对象,被加入导该函数的作用域链的最前端。函数执行解析变量时,会沿着作用域链依次寻找,直到找到位置。有一点需要注意,作用域链中的变量对象是一个引用,并没有把变量对象复制再单独存储一份。

上面我们作用域链的概念和函数寻找变量的机制说明白了,下面我们来看看resultFun[6]在定义和执行时都发生了什么。resultFun[6]初始化的时候,它的作用域链被创建,此时作用域链中包含两个变量对象,一个是createFunction的变量对象,一个是全局变量对象。createFunction的变量对象中有resulti两个变量。一般当一个函数执行完之后,它的变量对象都会被销毁,内存被回收。但是当createFunction执行完之后,result这个函数数组被赋值给全局的resultFun变量了,这10个函数还存在,而且这10个函数的作用域链中都有对createFunction的变量对象的引用,所以createFunction的变量对象不会被销毁。createFunction执行完之后,它的变量对象中的变量i等于10。也就是说resultFun这10个函数作用域链中引用的变量对象中的i等于10。所以无论调用哪个函数,结果都是10。

我们解释完为什么了,面试官可能会问你,怎么改一下让它达到我们的预期效果。答案如下:

function createFunction() {
    var result = new Array();

    for(var i=0; i < 10; i++){
        result[i] = function(num) {
            return function() {
                return num;
            };
        }(i);
    }

    return result;
}
var resultFun = createFunction();
复制代码

好,你再解释一下为什么这样就会达到预期效果(内心os:面试官好烦人)。我们上面的思路再看一下这个新函数,这个函数不同的地方在于给result赋值的函数,由一个匿名的函数立即执行表达式返回。我们可以发现最后result的值,其实是立即执行函数表达式的闭包。我们有10个立即执行函数表达式,就是10个对应的变量对象,result这10个函数的作用域链分别引用了这10个变量对象。我们知道,函数传参是按值传递,所以num和i没有关联关系。这10个变量对象中num的值分别是从1到10。所以resultFun[6]()返回的是6。

从以上分析可以看出,闭包会使外层函数的变量对象不销毁,占用内存空间,所以要注意不要滥用闭包。

应用

私有变量

js的对象没有私有属性这个概念,所有的属性都是公共的。我们可以利用闭包来达到私有变量的封装。原理其实很简单,我们在外部函数中定义一个变量,然后在函数内部定义函数,也就是闭包,闭包可以访问该变量,然后返回该函数。那么我们就只能通过这个闭包函数去访问这个变量,这个变量就是私有变量。这个函数就被成为特权函数。

一般有两种方式定义私有变量,一种是构造函数,另一种是立即执行函数表达式。

构造函数的私有变量
function Person(name){
    this.getName = function(){
        return name;
    };
    this.setName = function (value) {
        name = value;
    };
}
var person = new Person(“Nicholas”);
alert(person.getName()); //”Nicholas”
person.setName(“Greg”);
alert(person.getName()); //”Greg”
复制代码

函数参数name就是私有变量。每个实例上都有getName和setName,不共用。每个实例都对应不同的变量对象。这里需要说明一下的是,函数每执行一次都会生成一个变量对象,叫做活动对象。对于内部有闭包的函数,每执行一次,生成一个变量对象,且该变量对象不销毁,也就是说每执行一次,内存中就多一个变量对象。这种方式的确点就是实例的方法不共用,造成内存浪费。

静态私有变量

我们可以使用立即函数表达式来实现:

(function(){
    var name = "";
    Person = function(value){
        name = value;
    };
    Person.prototype.getName = function(){
        return name;
    };
    Person.prototype.setName = function (value){
        name = value;
    };
})();
var person1 = new Person(“Nicholas”);
alert(person1.getName()); //”Nicholas”
person1.setName(“Greg”);
alert(person1.getName()); //”Greg”
var person2 = new Person(“Michael”);
alert(person1.getName()); //”Michael”
alert(person2.getName()); //”Michael”
复制代码

我们可以看到现在每个实例的方法和私有变量都是共有的了。使用构造函数私有变量还是静态私有变量或者混合使用看你自己的需求。

模块模式(单例模式)

在只需要一个实例的情况下,我们可以这样:

var application = function(){
    //private variables and functions
    var components = new Array();
    //initialization
    components.push(new BaseComponent());
    //public interface
    return {
        getComponentCount : function(){
            return components.length;
        },
        registerComponent : function(component){
            if (typeof component == “object”){
                components.push(component);
            }
        }
    };
}();
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值