静态成员是指那些不需要实例化就可以访问的成员。静态成员也被称为类成员,而非静态成员因为需要实例化才能访问,所以被称为实例成员。
如果一个类的所有成员都是静态的,那么说这个类是静态类。
之所以要定义静态成员,是因为某些对象的特定行为是一成不变的。
在面向对象开发的第一个步骤中,也就是对象抽象为类这个步骤中,我们发现,有一些类虽然也是事物的抽象。
如果定义一个 Math 类,该类包含很多数学运算方法,对于其中的正弦 sin 方法不可能有第 2 种运算了,所有 Math 类的数学运算都是如此,这就意味着 Math 类不可能有个体存在,因此也就无需实例化, Math 就是一个静态类。静态类无需实例化就可以直接使用,例如下面的代码用来计算圆的面积:
var area = Math.PI _ radius_ radius;
这里就没有实例化 Math 类。
如果想把成员声明为静态的,只需将属性或者方法赋给类本身。
静态成员的一种用途是保留有关类及其实例的状态信息。例如,假设要跟踪从某个类创建的实例的数目。简单的实现方法就是使用一个静态属性,每当创建一个新实例时这个属性的值递增。
静态成员不能被继承,但是这些静态成员是子类作用域链中的一部分,它们可以传播到当前类的子类(注意,是传播,而不是继承,静态成员不能被子类继承)。这意味着在子类体中,不必引用在其中定义静态属性或方法的类,就可以使用静态属性或方法。
在前面的范例中,使用了一个类属性来跟踪所创建的该类的实例数。可以创建 CountTest 类的一个子类,例如下面的代码:
function SubCountTest() {
// 注意, i 并非继承而来
console.log('$1 建 subCountTest # ' + CountTest.i);
}
SubCountTest.prototype = new CountTest();
因为没有继承,那么也就不存在这个静态属性,因此不能使用子类访问,也即不能使用下面的语法:
console.log(SubCountTest.i); // 出错
console.log(oSubCountTest.i); // 出错
console.log(this.i); // 出错
使用静态成员的注意事项。
JavaScript 允许在同一类中定义同名的静态属性和实例属性。例如,下面的代码声明一个名为 i 的静态属性和一个同名的实例属性:
function CountTest() {
this.i = 1;
}
CountTest.i = 0;
在另一个程序中,可以分别访问这两个属性:
console.log('CountTest 静态属性 :' + CountTest.i); // 输出 0
var oCountTest = new CountTest();
console.log('CountTest 实例属性 :' + oCountTest.i); // 输出 1
由于不能在静态方法中使用 this 关键字,所以静态方法只能访问静态属性,而不能访问实例属性。例如,以下代码将导致编辑器错误,因为静态方法 getData() 引用了实例属性 i :
function CountTest() {
this.i = 1; // 实例属性
}
CountTest.getData = function () {
var data = this.i;
// 错误
//
return data;
};
如果一个类中的成员全部是静态成员,那么就可以说该类为静态类。
既然静态类包含的成员都是静态的,它们不能被实例化,因此只需在构造方法内包含一个 throw 语句,当用户企图实例化该类时就抛出异常。
下面的代码演示了一个完整的静态类的声明:
function CountTest() {
throw new Error('$1 是静态类,不能实例化!');
}
静态成员最常用的功能就是创建枚举,枚举是一组有意义的常量,常量值一般是相关的,为方便起见而将其作为一个类的属性组合在一起。
枚举就是静态常量。例如,可以为一周中的 7 天对应 7 个整数,这就可以声明一个枚举,为每个整数声明一个有意义的常量来表示,然后在代码中使用这 7 天的常量而不是它们的整数值。
例如下面的代码,使用类 Day 定义了星期的枚举值:
function Day() {
throw new Error('$1 是静态类,不能实例化! ');
}
Day.MONDAY = 1;
Day.TUESDAY = 2;
Day.WEDNESDAY = 3;
Day.THURSDAY = 4;
Day.FRIDAY = 5;
Day.SATURDAY = 6;
Day.SUNDAY = 0;
// 然后在程序中就可以像使用常量那样使用枚举,
// 例如下面的代码:
var oDate = new Date();
var day = oDate.getDay(); // 获取当前的星期数
switch (day) {
case Day.SATURDAY:
console.log('$1 天周末 ');
break;
case Day.SUNDAY:
console.log('$1 天周末 ');
break;
default:
console.log('$1 天是工作日 ');
break;
}
使用枚举可使代码更清楚、更易读,当枚举是有意义的名称时尤其如此。总结起来,使用枚举有如下优点:
由于使用有意义的名称,所以使用枚举更易于发现字面错误,从而减少由于文本字符串或数字值输入错误所引起的错误。并且,如果枚举名输入错误, JavaScript 解释引擎会生成一个错误,因为枚举是一个静态属性,当拼写错误时,实际上该属性不存在,所以就会导致执行错误。如果使用文本字符串或数字值,存在字符串拼写错误或使用了错误数字时,解释引擎并不会报错。例如我们定义的处理星期的条件运算:
var oDate = new Date();
var day = oDate.getDay();
// 获取当前的星期数
switch (day) {
case Day.SATURDAY: // 枚举如果输入错误,就会导致执行错误
console.log('$1 天周末 ');
break;
case 9: // 9 不是星期数的一个合理值,但不会导致执行错误
console.log('$1 天周末 ');
break;
default:
console.log('$1 天是工作日 ');
break;
}
更容易使用。因为 IDE 可以像对待类的属性和方法那样,自动识别并列出类中定义的枚举,所以避免了输入的错误。 枚举一般被分类定义在特定的类中,所以便于以后的修改。 使代码更易读,这意味着代码中发生错误的概率降低。 确保向前兼容性。如果某个方法、属性所使用的枚举值改变了,那么仅仅改变枚举的定义就可以保证原有程序正常运行,从而降低了出错概率,减少了维护的工作量。
使用 ES6 中的符号 Symbol.iterator 来获取对象的 @@iterator 内部属 性。 引用类似 iterator 的特殊属性时要使用符号名,而不是符号包含的 值。此外,虽然看起来很像一个对象,但是 @@iterator 本身并不是一个迭代 器对象,而是一个返回迭代器对象的函数——这点非常精妙并且非常重要