第七章 函数表达式(4.私有变量)

  ·   JS高设(第三版)   ·   JavaScript     浏览量:

概念

严格来说,js中没有私有成员的概念;所有对象属性都是公有的。不过倒是有一个私有变量的概念。
在任何函数中定义的变量,都可以认为是私有的,不能在函数外部访问。私有变量包括函数的参数、局部变量和在函数内部定义的函数。
但是可以通过闭包创建访问私有变量的公有方法,这就是特权方法(privileged method)。有两种在对象上创建特权方法的方式。第一种就是在构造函数中定义特权方法,基本模式如下。

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

    function privateFunction() {
        return false;
    }

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

上面代码中,除了使用publicMethod(),这一个途径,没有任何办法直接访问privateVariable和privateFunction()

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

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

var person = new Person("尼古拉斯");
alert(person.getName()); // 弹出 尼古拉斯
person.setName("德古拉");
alert(person.getName()); // 弹出 德古拉

以上的getName和setName两个方法都有权访问私有变量name,但在Person构造函数外部,没有任何办法访问name。私有变量在每个Person的实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。不过在函数中定义特权方法也有一个缺点,那就是你必须使用构造函数来达到这个目的。
第六章讨论过,构造函数模式的缺点是针对每个函数都会创造同一组新方法,而静态私有变量来实现特权方法就可以避免这个问题。

静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。

(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('尼古拉斯');
console.log(person1.getName()); // '尼古拉斯'
person1.setName('格雷'); // '格雷'
console.log(person1.getName())

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

多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个明显不足之处。

模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。而道格拉斯所说的(这人谁啊)模块模式则是为单例创建私有变量和特权方法。所谓单例就是指只有一个实例的对象。按照惯例js是以对象字面量来创建单例对象的。

var singleton = {
    name: value,
    method: function () {
        // 这里是方法的代码
    }
}

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

var singleton = function () {

    // 私有变量和函数
    var privateVariable = 10;

    function privateFunction () {
        return false;
    }

    // 特权/公有方法和属性
    return {
        publicProperty: true,

        publicMethod: function () {
            privateVariable++;
            return privateFunction();
        }
    }
}

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

 var application = function () {
    //  私有变量和函数
    var components = new Array();

    // 初始化
    components.push(new BaseComponent());

    // 公共
    return {
        getComponent: function () {
            return components.length;
        },

        registerComponent: function (component) {
            if(typeof component == "object") {
                component.push(component);
            }
        }
    }
 }();

在web应用程序中,经常需要使用一个单例来管理应用级的信息。这个简单的例子创建开了一个用于管理组件的application对象。在创建这个对象的过程中,首先声明了一个私有的components数组,并向数组中添加了一个BaseComponent的新实例(BaseComponent只是一个展示,不用关心详细代码)。而返回对象的getComponent()和rigisterComponent()方法,都是有权访问数组components的特权方法。前者只是返回已注册的组件数,后者用于注册新的组件。
简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,name就可以使用模块模式。以这种模式创造的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示他。事实上这也没什么,因为单例通常是作为全局对象存在的,我们不会将它传给一个函数。因此也就没什么必要使用instance操作符来检查对象类型了。

增强的模块模式

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



收起 >>
第七章 函数表达式(4.私有变量)