在阅读这篇文章之前,建议先阅读JavaScript的作用域链以及JavaScript闭包–简介。
正如闭包的定义一样:“闭包指的是有权访问另一个函数作用域中的变量的函数”, 闭包最大的意义就在于闭包可以对另一个函数作用域的变量进行访问,由此,闭包可以延伸出一系列的用法。
严格来说,JavaScript没有私有成员的概念,所有对象属性都是公共的。不过JavaScript有私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外面访问这些变量。
私有变量包括:
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
在这个函数内部,私有变量有3个:num1, num2, sum。在函数内部可以访问这几个变量,但是在函数外部则不能访问。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。利用这一点,就可以创建用于访问私有变量的公有方法。
这种有权访问私有变量和私有函数的公有方法被称为特权方法(privileged method)。
function Person(value) {
var name = value;
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"
以上代码的构造函数中定义了两个特权方法:getName() & setName()。这两个方法可以通过对象访问,而且都有权访问私有变量name。但是在Person构造函数外部,没有任何方法可以访问name。由于这两个方法是在构造函数内部定义的,因此作为闭包能够通过作用域链访问到name。
这种方法最大的问题在于,必须使用构造函数来创建一个对象,这样才会在执行这个构造函数的时候,生成构造函数的活动对象,里面保存着变量name,从而使得闭包可以通过作用域链引用构造函数的活动对象,从而得以访问变量name。
利用私有和特权成员,可以隐藏哪些不应该被直接修改的数据。
静态私有变量是所有实例共享的。由于特权方法是在原型中定义的,因此所有实例都使用同一个函数。
(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());
person1.setName("Greg");
console.log(person1.getName());
var person2 = new Person("Michael");
console.log(person1.getName());
console.log(person2.getName());
通过函数包装器创建了一个私有作用域,有3个特权方法:Person构造函数 & getName() & setName() 方法作为闭包是可以访问私有变量name的,但是函数外部不能直接访问变量name,必须通过3个特权方法来访问。
在这种模式下,变量name变成了一个静态的,由所有实例共享的属性。因为所有的实例通过特权方法访问的变量name都是函数包装器的活动对象中的变量name。
因为需要保存私有变量name,所以需要额外的一个私有作用域,闭包的作用域链就必须增加一个层次。
多查找作用域链中的一个层次,就会在一定程度上影响查找速度。这正是使用闭包和私有变量的一个明显的不足之处。