JavaScript中的块级作用域和私有变量

来源:互联网 发布:win10精简优化版 编辑:程序博客网 时间:2024/04/29 12:34

模仿块级作用域:

JavaScript没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。

看一个例子:

 function outputNumbers(count){

         for(vari=0;i<count;i++){

           alert(i);

}

alert(i); //计数

}

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

function outputNumbers(count){

         for(vari=0;i<count;i++){

           alert(i);

}

var i; //重新声明变量

alert(i); //计数

}

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

匿名函数可以用来模仿块级作用域并避免这个问题。

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

(function(){

  //这里是块级作用域

})();

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

这种不太好理解的语法的由来:

下面的代码会导致错误:

function(){

  //这里是块级作用域

}(); //出错!

这段代码会导致语法错误,是因为JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要像下面这样给它加上一对圆括号即可:

(function(){

  //这里是块级作用域

})();

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

 

 

function outputNumbers(count){

(function(){

for(var i=0;i<count;i++){

         alert(i);

}

   })();

alert(i); //导致错误!

}

在这个重写后的outputNumbers()函数中,我们在for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

 

 

私有变量:

严格来说,JavaScript中没有私有成员的概念,所有对象属性都是共有的。不过倒是有私有变量的概念。

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

看一个例子:

function add(num1,num2){

  var sum=num1+num2;

     return sum;

}

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

把有权访问私有变量和私有函数的公有方法称为特权方法。

下面是第一种在构造函数中定义特权方法的模式:

function MyObject(){

     //私有变量和私有函数

     var privateVariable=10;

     functionprivateFunction(){

         return false;

}

//特权方法

this.publicMethod=function(){

         privateVariable++;

         returnprivateFunction();

};

}

这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量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(“Nicholas”);

alert(person.getName()); //“Nicholas”

person.SetName(“Greg”);

alert(person.getName()); //“Greg”

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

在构造函数中定义特权方法有一个缺点,那就是你必须使用构造函数模式来达到这个目的。

静态私有变量:

第二种创建特权方法的方法是,通过在私有作用域中定义私有变量或函数:

(function (){

     //私有变量和私有函数

     var privateVariable=10;

     functionprivateFunction(){

         return false;

}

//构造函数

MyObject=function(){

};

//公有/特权方法

MyObject.prototype.publicMethod=function(){

         privateVariable++;

         returnprivateFunction();

};

})();

这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没有在声明MyObject时使用关键字var。(记住,初始化未经声明的变量,总是会创建一个全局变量。)因此,MyObject就成了一个全局变量,能够在私有作用域之外被访问到。(但也要知道,在严格模式下给未经声明的变量赋值会导致错误。)

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

(function (){

     var name=””;

Person=function(value){

name=value;

};

Person.prototype.getName=function(){

         return name;

};

Person.prototype.setName=function(value){

         name=value;

};

  })();

 var person1=newPerson(“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”

这个例子中的Person构造函数与getName()和setName()方法一样,都有权访问私有变量name。在这种模式下,变量name就变成了一个静态的、由所有实例共享的属性。也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。

 

模块模式:

(前面的模式是用于为自定义类型创建私有变量和特权方法的。)而模块模式则是为单例创建私有变量和特权方法。所谓单例,指的就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来创建单例对象的:

  varsingleton={

         name:value;

         method:function(){

         //这里是方法的代码

}

};

模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:

varsingleton=function(){

     //私有变量和私有函数

var privateVariable=10;

     functionprivateFunction(){

         return false;

}

//特权/公有方法和属性

return{

         publicProperty:true;

publicMethod:function(){

                  privateVariable++;

                  return privateFunction();

}

};

}();

这个模块模式使用了一个返回对象的匿名函数,在这个匿名函数内部,首先定义了私有变量和函数,然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质上讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。

下面看一个例子:

var application=function(){

     //私有变量和函数

     var components=newArray();

     //初始化

     components.push(newBaseComponent());

     //公共

     return{

         getComponentCount:function(){

         returncomponents.length;

},

registerComponent:function(component){

         if(typeof component==”object”){

         components.push(component);

}

}

};

}();

这个简单的例子创建了一个用于管理组件的application对象。在创建这个对象的过程中,首先声明了一个私有的components数组,并向数组中添加了一个BaseComponent的新实例(在这里不需要关系BaseComponent的代码,我们只是用它来展示初始化操作)。而返回对象的getComponentCount()和registerComponent()方法,都是有权访问数组components的特权方法,前者只是返回已注册的组件数目,后者用于注册新组建。

简单的说就是,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。

 

增强的模块模式:

进一步改进模块模式,即在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。

来看一个例子:

var singleton=function(){

     //私有变量和私有函数

var privateVariable=10;

     functionprivateFunction(){

         return false;

}

//创建对象

var object=new CustomType();

//添加特权/公有属性和方法

object.publicProperty=true;

object.publicMethod=function(){

         privateVariable++;

         return privateFunction();

};

return object;

}();

如果前面演示模块模式的例子中的application对象必须是BaseComponent的实例,那么就可以使用以下代码:

var application=function(){

     //私有变量和函数

     var components=newArray();

     //初始化

     components.push(newBaseComponent());

     //创建application的一个局部副本

     var app=new BaseComponent();

     //公共接口

app.getComponentCount=function(){

return components.length;

},

app.registerComponent=function(component){

if(typeof component==”object”){

components.push(component);

}

};

         //返回这个副本

         retutnapp;

}();

在这个重写后的application单例中,首先也是像前面例子中一样定义了私有变量。主要的不同之处在于命名变量app的创建过程,因为它必须是BaseComponent的实例。这个实例实际上是application对象的局部变量版。此后,我们又为app对象添加了能够访问私有变量的公有方法。最后一步是返回app对象,结果仍然是将它赋值给全局变量application。

 

转自:http://blog.csdn.net/xin_xun_/article/details/60467773

 

0 0
原创粉丝点击