7.4 私有变量
严格来讲,JavaScript没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。
私有变量包括包含的参数、局部变量和在函数内部定义的其他函数。
1
2
3
4
5
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
//在这个函数内部有三个内部变量:num1,num2和sum。在函数内部可以访问这三个变量,但在函数外部不能访问它们。利用这一点,可以创建用于访问私有变量的公有方法。
我们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。
有两种在对象上创建特权的方法:第一种是在构造函数中定义特权方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function(){
privateVariable++;
return privateFunction();
};
}
//这个模式在构造函数内部定义了所有私有变量和函数。然后有继续创建了能够访问这些私有成员的特权方法。
//能够在构造函数中定义特权方法,是因为特权方法作为闭包有权限访问在构造函数中定义的所有变量和函数。
//除了使用publiceMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction()。
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name){
this.getName = function(){
return name;
};
this.setName = function(value){
name = value;
};
}
var person = new Person('Nicholas');
console.log(person.getName()); //'Nicholas'
person.setName('Greg');
console.log(person.getName()); //'Greg'
//私有变量name在Person的每一个实例中都不同,因为每次调用构造函数都会重新创建这两个方法。
在构造函数中定义特权方法也有一个缺点,那就是必须使用构造函数模式来达到这个目的。第六章讨论过,构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而第二种方法就是使用静态私有变量来实现特权方法就可以避免这个问题。
1. 静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数
MyObject = function(){
};
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
这个模式创建了一个私有作用域,并在其中封装了一个函数及相应的方法。在私有作用域中,首先定义了私有变量和函数,然后又定义构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。也没有用var,所以会创建一个全局变量(严格模式下会导致错误)。
这个模式与构造函数中定义特权方法的注意区别,就在于私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(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');
console.log(person1.getName()); //'Nicholas'
person1.setName('Greg');
console.log(person1.getName()); //'Greg'
var person2 = new Person('Michael');
console.log(person1.getName()); //共享name现在变成了'Michael'
console.log(person2.getName()); //'Michael'
这个例子中Person与getName()和setName()方法一样,都用权访问私有变量name。在这个模式下,变量name就变成了一个静态的、由所有实例共享的属性。也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。
以这种方式创建静态私有变量会因为使用原型而增加代码复用,但是每个实例都没有自己的私有变量。
多查找作用域中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个显明的不足之处。
2. 模块模式
前面的模式是用于为自定义类型创建私有变量和特权方法的。而道格拉斯所说的模块模式(module pattern)则是为单例创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来创建单例对象的。
1
2
3
4
5
6
var singleton = {
name: 'value',
method: function(){
//这里是方法的代码
}
};
模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var singleton = function() {
var privateVariable = 10;
function privateFunction(){
return false;
}
return {
publicProperty: true,
publicMethod: function(){
privateVariable++;
return privateFunction();
}
};
}();
这个模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。从本质上讲这个对象字面量是单例的公共接口。
这种模式对于需要对单例进行某些初始化,同时又需要维护其私有变量时非常有用的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//公共
return {
getComponentCount: function(){
return components.length;
},
registerComponent: function(component){
if(typeof component == 'object'){
components.push(component);
}
}
}
}
简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还有公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。
3. 增强的模块模式
即在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var singleton = function(){
//私有变量和函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//创建对象
var object = new CustomType();
//添加特权/公有属性和方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
//返回这个对象
return object;
}
如果前面掩饰模块模式的例子中的application对象必须是BaseComponent的实例,那么就可以使用以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var application = function(){
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//创建application的一个局部副本
var app = new BaseComponent();
//公共接口
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function(component){
if(typeof component == 'object'){
components.push(component);
}
};
//返回这个副本
return app;
}
【本文内容摘自:《JavaScript高级程序设计》(第3版)Nicholas C.Zakas 著 李松峰 曹力 译】