闭包的应用

闭包的两大应用:模仿块级作用域和创建私有变量。
(1)模仿块级作用域
JavaScript没有块级作用域的概念。这意味着在块级语句中定义的变量,实际上是在包含函数中而非语句中创建。

function test(){
    for(var i=0; i<4; i++){
        alert(i);
    }

    alert("i="+i);
}

test();   //0,1,2,3,i=4

函数中定义了一个for循环,而变量i的初始值被设置为0。在Java、C++等语言中,变量i只会在for循环的语句块中有定义,循环一旦结束,变量i就会被销毁。但在JavaScript中,变量i是定义在test()活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。即使像下面这样错误地声明同一个变量,也不会改变它的值。

function test(){
    for(var i=0; i<4; i++){
        alert(i);
    }

    var i;    //重新声明变量
    alert("i="+i);
}

test();   //0,1,2,3,i=4

JavaScript从来不会告诉你是否多次声明了同一个变量。遇到这种情况,它只会对后续的声明视而不见,不过,它会执行后续声明中的变量初始化。

function test(){
    for(var i=0; i<4; i++){
        alert(i);
    }

    var i = 20;  //声明变量并初始化
    alert("i="+i);
}

test();   //0,1,2,3,i=20

如何解决上述的问题? 匿名函数可以用来模仿块级作用域并避免这个问题。

用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示。

(function(){
    //块级作用域
})();

以上代码定义并立即调用一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧接其后的另一对圆括号会立即调用这个函数。

那么,可以在函数声明的后面直接加一个圆括号吗?就像下面这样

var somFunction = function(){
    //块级作用域
};

someFunction();  //调用函数

定义函数的方式是创建一个匿名函数,并把匿名函数赋值给变量someFunction。而调用函数的方式是在函数名后面添加一对圆括号,即someFunction()。那么,可以用函数的值直接取代函数名吗?

function(){
    //块级作用域
}();   //出错

JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式后面可以跟圆括号。要将函数声明转换成函数表达式,只需要像下例一样加一个圆括号即可。

(function(){
    //块级作用域
})();

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。例如:

function test(){
    (function(){
        for(var i=0; i<4; i++){
            alert(i);
        }
    })();

    alert("i="+i);
}

test();   //出错

在重写后的test()函数中,for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。

这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。

(2)定义私有变量
JavaScript中没有私有成员的概念,所有对象属性都是共有的。但有私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数,局部变量和在函数中定义的其它函数。

function add(num1, num2){
    var sum = num1 + num2;
    return sum;
}

在这个函数内部,有3个私有变量:num1、num2和sum。在函数内部可以访问这几个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,就可以创建用于访问私有变量的公有方法。

有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式。第一种是在构造函数中定义特权方法,基本模式如下。

function MyObject(){
    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    //特权方法
    this.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
}

这个模式在构造函数内部定义了私有变量和函数。然后,又继续创建了能够访问这些私有成员的特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。变量privateVariable和函数privateFunction()只能通过特权方法publicMethod()来访问。在创建MyObject的实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction()。

利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。

function Person(name){
    this.getName = function(){
        return name;
    }

    this.setName = function(value){
        name = value;
    }
}

var person = new Person("John");
alert(person.getName());  //"John"
person.setName("Grey");
alert(oerson.getName());  //"Grey"

以上代码的构造函数中定义了两个特权方法:getName()和setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name。但在Person函数的外部,没有任何办法访问name。由于这两个方法是在构造函数内部定义的,它们作为闭包能够通过作用域链访问name。私有变量name在Person的每一个实例中都不同,因为每次调用构造函数都会重新创建这两个方法。

不过,在构造函数定义特权方法也有一个缺点,那就是必须使用构造函数模式来达到目的。构造函数的缺点是针对每个实例都会创建同样一组新方法,而是用静态私有变量来实现特权方法就可避免这个问题。

静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下所示。

(function(){
    //私有变量和私有属性
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    //构造函数
    MyObject = function(){  //全局变量
    };

    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    }
})();

这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有属性,然后又定义了构造方法及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数。

这个模式与在构造函数中定义的特权方法的主要区别,就在于私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都是用同一个函数。而这个特权方法。作为一个闭包,总是保存着对包含作用域的引用。

(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("John");
alert(person1.getName());   //"John"
person1.setName("Grey");
alert(person1.getName());   //"Grey"

var person2 = new Person("John");
alert(person1.getName());   //"John"
alert(person2.getName());   //"John"

这个例子中的Person构造函数与getName()和setName()方法一样,都有权访问私有变量name。在这种模式下,变量name就变成一个静态的、由所有实例共享的属性。也就是说,在一个实例上调用setName()会影响所有的实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。

以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。

参考资料:《JavaScript高级程序设计》(第三版)
(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值