代码改变世界

JavaScript基础对象创建模式之静态成员(027)

2014-09-17 00:21  Bryran  阅读(270)  评论(0编辑  收藏  举报

在支持“类”的面向对象语言中,静态成员指的是那些所有实例对象共有的类成员。静态成员实际是是“类”的成员,而非“对象”的成员。所以如果 MathUtils类中有个叫 max()的静态成员方法,那么调用这个方法的方式应该是这样的:MathUtils.max(3, 5)。

1. 公有静态成员
JavaScript里并没有“类”的实际语言表示 ,所以也就没有静态成员的语义表示。但由于构造函数本身就是个对象,如果把构造函数看成“类”,那么它的成员就是可以通过“类”名(也就是构造函数)直接访问的“静态成员”。函数的记忆模式用的也是相同的技术。下面的例子中,Gadget的构造函数提供一个公有的静态方法isShiny()以及一个常规的实例方法setPrice():
// constructor
var Gadget = function () {};
// a static method
Gadget.isShiny = function () {
    return "you bet";
};
// a normal method added to the prototype
Gadget.prototype.setPrice = function (price) {
    this.price = price;
};
调用Gadget的这两个方法时,由于isShiny()是静态方法,所以只能通过“类”,也就是构造函数来调用,而setPrice()是实例方法,必须用创建出的对象来调用:
// calling a static method
Gadget.isShiny(); // "you bet"
// creating an instance and calling a method
var iphone = new Gadget();
iphone.setPrice(500);
反过来,用“类”调用实例方法,或用对象调用静态方法,得到的都是undefined:
typeof Gadget.setPrice; // "undefined"
typeof iphone.isShiny;  // "undefined"
如果需要在实例中调用静态方法,其实也很简单,只需让构造函数的prototype支持这个静态方法即可:
Gadget.prototype.isShiny = Gadget.isShiny;
iphone.isShiny(); // "you bet"
当用prototype来调用静态方法时需要特别注意,在静态方法中的this引用是构造函数Gadget,而普通的实例方法中的this引用是调用它的对象本身。据此,我们可以创建出一个方法,它既可静态地调用,也可以用对象调用,分别实现不同的功能:
// constructor
var Gadget = function (price) {
    this.price = price;
};
// a static method
Gadget.isShiny = function () {
    // this always works
    var msg = "you bet";
    if (this instanceof Gadget) {
        // this only works if called non-statically
        msg += ", it costs $" + this.price + '!';
    }
    return msg;
};
// a normal method added to the prototype
Gadget.prototype.isShiny = function () {
    return Gadget.isShiny.call(this);
};
这时,如果把isShiny()作为静态方法调用,结果是这样的:
Gadget.isShiny(); // "you bet"

 而非静态的调用,也就是用对象来调用这个方法,结果则变成:

var a = new Gadget('499.99');
a.isShiny(); // "you bet, it costs $499.99!"

 

2. 私有静态成员
私有静态成员指的是这些静态成员:
  所有实例对象都可以通过“类名“(也就是构造函数)来访问;
  在构造函数之外不能被访问。
 
计数器是常见的私有静态属性。为了提供私有的空间,需要用一个即时函数来提供局部作用域:
var Gadget = (function () {
    // static variable/property
    var counter = 0;
    // returning the new implementation
    // of the constructor
    return function () {
        console.log(counter += 1);
    };
}()); // execute immediately
这样,每次创建新的实例,内部的静态成员counter就会加一:
var g1 = new Gadget(); // logs 1
var g2 = new Gadget(); // logs 2
var g3 = new Gadget(); // logs 3
这样,counter也可以作为每次新创建的对象的ID:因为它不重复,而且还自增。这时可以考虑加一个可以给所有实例对象使用的访问控制方法getLastID()来访问这个ID:
// constructor
var Gadget = (function () {
    // static variable/property
    var counter = 0,
        NewGadget;
    // this will become the
    // new constructor implementation
    NewGadget = function () {
        counter += 1;
    };
    // a privileged method
    NewGadget.prototype.getLastId = function () {
        return counter;
    };
    // overwrite the constructor
    return NewGadget;
}()); // execute immediately

 

测试一下:
var iphone = new Gadget();
iphone.getLastId(); // 1
var ipod = new Gadget();
ipod.getLastId();   // 2
var ipad = new Gadget();
ipad.getLastId();   // 3

 

公有和私有的静态成员可以为每一位实例对象提供共同的方法和属性,这些方法和属性不会在每次创建对象的时候多次初始化,因此在实际应用中十分有用。后面还将介绍的单例模式使用到了静态成员的技术。