静态成员
静态属性和方法是指那些在所有的实例中都一样的成员。在基于类的语言中,静态成员是用专门的语法来创建,使用时就像是类自己的成员一样。比如MathUtils
类的max()
方法会被像这样调用:MathUtils.max(3, 5)
。这是一个公有静态成员的示例,即可以在不实例化类的情况下使用。同样也可以有私有的静态方法,即对类的使用者不可见,而在类的所有实例间是共享的。我们来看一下如何在JavaScript中实现公有和私有静态成员。
公有静态成员
在JavaScript中没有专门用于静态成员的语法。但通过给构造函数添加属性的方法,可以拥有和基于类的语言一样的使用语法。之所以可以这样做是因为构造函数和其它的函数一样,也是对象,可以拥有属性。前一章讨论过的记忆模式也使用了同样的方法,即给函数添加属性。
下面的例子定义了一个构造函数Gadget()
,它有一个静态方法isShiny()
和一个实例方法setPrice()
。isShiny()
是一个静态方法,因为它不需要指定一个具体的对象就能工作(你不需要先拿到一个特定的小工具(gadget)才知道所有小工具是不是有光泽的(shiny))。但setPrice()却需要一个对象,因为小工具可能有不同的定价:
// 构造函数
var Gadget = function () {};
// 静态方法
Gadget.isShiny = function () {
return "you bet";
};
// 添加到原型的普通方法
Gadget.prototype.setPrice = function (price) {
this.price = price;
};
现在我们来调用这些方法。静态方法isShiny()
可以直接在构造函数上调用,但其它的方法需要一个实例:
// 调用静态方法
Gadget.isShiny(); // "you bet"
// 创建实例并调用方法
var iphone = new Gadget();
iphone.setPrice(500);
使用静态方法的调用方式去调用实例方法并不能正常工作,同样,用调用实例方法的方式来调用静态方法也不能正常工作:
typeof Gadget.setPrice; // "undefined"
typeof iphone.isShiny; // "undefined"
有时候让静态方法也能用在实例上会很方便。我们可以通过在原型上加一个新方法来很容易地做到这点,这个新方法作为原来的静态方法的一个包装:
Gadget.prototype.isShiny = Gadget.isShiny;
iphone.isShiny(); // "you bet"
在这种情况下,你需要很小心地处理静态方法内的this
。当你运行Gadget.isShiny()
时,在isShiny()
内部的this
指向Gadget
构造函数。而如果你运行iphone.isShiny()
,那么this
会指向iphone
。
下面的例子展示了同一个方法被静态调用和非静态调用时明显不同的行为,这取决于调用的方式。这里的instanceof
用于获取方法是如何被调用的:
// 构造函数
var Gadget = function (price) {
this.price = price;
};
// 静态方法
Gadget.isShiny = function () {
// 这句始终正常工作
var msg = "you bet";
if (this instanceof Gadget) {
// 这句只有在非静态方式调用时正常工作
msg += ", it costs $" + this.price + '!';
}
return msg;
};
// 原型上添加的方法
Gadget.prototype.isShiny = function () {
return Gadget.isShiny.call(this);
};
测试一下静态方法调用:
Gadget.isShiny(); // "you bet"
测试一下实例中的非静态调用:
var a = new Gadget('499.99');
a.isShiny(); // "you bet, it costs $499.99!"
私有静态成员
到目前为止,我们都只讨论了公有的静态方法,现在我们来看一下如何实现私有静态成员。所谓私有静态成员是指:
- 被所有由同一构造函数创建的对象共享
- 不允许在构造函数外部访问
我们来看一个例子,counter
是Gadget()
构造函数的一个私有静态属性。在本章中我们已经讨论过私有属性,这里的做法也是一样,需要一个函数提供的闭包来包裹私有成员。然后让这个包裹函数立即执行并返回一个新的函数。将这个返回的函数赋值给Gadget()
作为构造函数。
var Gadget = (function () {
// 静态变量/属性
var counter = 0;
// 返回构造函数的新实现
return function () {
console.log(counter += 1);
};
}()); // 立即执行
这个Gadget()
构造函数只简单地增加私有变量counter
的值然后打印出来。用多个实例测试的话你会看到counter
在实例之间是共享的:
var g1 = new Gadget();// logs 1
var g2 = new Gadget();// logs 2
var g3 = new Gadget();// logs 3
因为我们在创建每个实例的时候counter
的值都会加1,所以它实际上成了唯一标识使用Gadget
构造函数创建的对象的ID。这个唯一标识可能会很有用,那为什么不把它通过一个特权方法暴露出去呢?(译注:严格来讲,这里不能叫ID,只是一个记录有多少个实例的数字而已,因为如果有多个实例被创建的话,没有办法取到除了最后一个之外的实例的标识。)下面的例子是基于前面的例子,增加了用于访问私有静态属性的getLastId()
方法:
// 构造函数
var Gadget = (function () {
// 静态变量/属性
var counter = 0,
NewGadget;
// 这将是Gadget的新实现
NewGadget = function () {
counter += 1;
};
// 特权方法
NewGadget.prototype.getLastId = function () {
return counter;
};
// 重写构造函数
return NewGadget;
}()); // 立即执行
测试这个新的实现:
var iphone = new Gadget();
iphone.getLastId(); // 1
var ipod = new Gadget();
ipod.getLastId(); // 2
var ipad = new Gadget();
ipad.getLastId(); // 3
静态属性(包括私有和公有)有时候会非常方便,它们可以包含和具体实例无关的方法和数据,而不用在每次实例中再创建一次。当我们在第七章中讨论单例模式时,你可以看到使用静态属性实现类式单例构造函数的例子。