我该如何在ES6类中使用静态变量?

45

我想在 ES6 中使用静态变量。我想在 Animal 类中声明一个名为 count 的静态变量并对其进行增加。但是,通过 static count = 0; 无法声明静态变量,所以我尝试另一种方法:

class Animal {
  constructor() {
    this.count = 0;
  }

  static increaseCount() {
    this.count += 1;
  }

  static getCount() {
    return this.count;
  }
}

console.log(Animal.increaseCount()); // undefined
console.log(Animal.getCount()); // NaN

我原本期望 console.log(Animal.getCount()); 的输出结果是 1,但它并没有正常工作。 那么我该如何声明一个静态变量,并通过调用方法来修改它呢?


请看以下关于 JavaScript 中静态变量的问题:https://dev59.com/43I_5IYBdhLWcg3wEe1u - Sébastien Palud
6
undefined + 1 = NaN 的翻译:undefined + 1 = NaN - Keith
7个回答

39
(注意:当涉及到子类时,类上的可变静态属性可能会有问题。有关详细信息,请参见答案末尾。)
你的类没有静态变量(如果你指的是静态属性的话)。在调用increaseCount之后,getCount返回NaN,因为Animal最初没有count属性。然后,increaseCount执行undefined + 1,结果是NaN,并将其赋值给Animal.count。通过new Animal创建的实例最初具有count属性,但Animal本身在调用increaseCount之前没有count属性。在静态方法中,this指的是Animal类(构造函数)本身(如果通过Animal.methodName(...)调用它)。
要给类添加静态属性,可以使用static关键字进行声明(现在类字段提案¹已经广泛实现)。

class Animal {
    static count = 0;

    static increaseCount() {
        ++this.count;
    }

    static getCount() {
        return this.count;
    }
}

Animal.increaseCount();
console.log(Animal.getCount());
Animal.increaseCount();
console.log(Animal.getCount());

如果你愿意的话,你可以将静态成员设为私有,这样就无法从外部访问Animal了。

class Animal {
    static #count = 0;

    static increaseCount() {
        ++this.#count;
    }

    static getCount() {
        return this.#count;
    }
}

Animal.increaseCount();
console.log(Animal.getCount());
Animal.increaseCount();
console.log(Animal.getCount());


关于上面的警告:在静态方法中使用this来引用类(构造函数)有点棘手,如果有子类的话,因为例如,如果你有:
class Mammal extends Animal {}

然后

Mammal.increaseCount();

thisincreaseCount(它从 Animal 继承而来)中指的是 Mammal,而不是 Animal

class Animal {
    static count = 0;

    static increaseCount() {
        ++this.count;
    }

    static getCount() {
        return this.count;
    }
}

class Mammal extends Animal {
}

console.log(Object.hasOwn(Mammal, "count")); // false
// This call *reads* from `Animal.count` (because `Mammal` doesn't have a
// `count` yet), but *writes* to `Mammal.count`, potentially causing
// confusion.
Mammal.increaseCount();
console.log(Object.hasOwn(Mammal, "count")); // true
console.log(Mammal.getCount()); // 1
// This time, both the read and write use `Mammal.count`
Mammal.increaseCount();
console.log(Mammal.getCount()); // 2
console.log(Animal.getCount()); // 0 - nothing ever wrote to it

如果你想要那种行为,使用`this`。如果不想要,那么在那些静态方法中使用`Animal`。
最后,我要注意的是,如果你将`count`设为私有字段,试图通过`Mammal`调用`increaseCount`将会失败,因为`Mammal`无法写入`Animal.#count`。

class Animal {
    static #count = 0;

    static increaseCount() {
        ++this.#count;
    }

    static getCount() {
        return this.#count;
    }
}

class Mammal extends Animal {
}

Mammal.increaseCount();
// ^^^^^^^^^^^^^^^−−− TypeError: Cannot read private member #count from an
// object whose class did not declare it


¹ 这个“类字段”提案是最初分开的多个提案的合并:私有实例方法和访问器, 类公共实例字段和私有实例字段, 静态类字段和私有静态方法

2
Animal类中允许使用static count = 0;吗?这会导致SyntaxError: Unexpected token错误。我正在使用Webpack的Babel。 - Simon Park
@Caesium133 - 正如我上面所说的,这是静态类字段提案的一部分,目前处于第三阶段的进程中(因此,它还没有包含在规范中,而引擎现在才开始添加它)。 - T.J. Crowder
如何将increaseCount放置在新Animal的构造函数中?这不是人们通常希望在类上使用计数器的原因吗?看起来你没有展示那种情况。(虽然我认为count应该是集合的属性,而不是实例类的属性——将count作为类的静态属性是一种“低成本”的集合,对吧?) - johny why
@johnywhy - 看起来不是原帖作者的使用情况。 - T.J. Crowder
1
static count = 0; 看起来现在可行了...我是不是在做梦?然而,它与Java的静态非常不同。 - OhadR
3
不,你没有做梦。 :-) V8(在 Chrome、Node.js、Brave、Chromium、新版 Edge 等)和 SpiderMonkey(在 Firefox 75+ 中)都已开始支持 static 公共字段。Safari 正在进行中。 - T.J. Crowder

14

如其他答案所述,this.count指的是constructor中的实例属性。为了初始化静态属性,需要设置Animal.count

类字段提案Animal.count = 0提供语法糖,可通过转译器(如Babel等)使用:

class Animal {
  static count = 0;
  ...
}

ES6中的另一种方法是使用初始值,这种情况下Animal.count的初始值不需要显式设置,例如:

class Animal {    
  static increaseCount() {
    this.count = this.getCount() + 1;
  }

  static getCount() {
    return this.count || 0;
  }
}

在JavaScript类中不欢迎访问器方法-这就是getter/setter描述符存在的原因:

class Animal {    
  static increaseCount() {
    this.count += 1;
  }

  static get count() {
    return this._count || 0;
  }

  static set count(v) {
    this._count = v;
  }
}

纯静态类在JavaScript中被认为是反模式,因为它们没有使用与类特定的状态或其他特征。如果只需要一个实例,则应该使用普通对象(除非有其他关注点可以受益于class):

const animal = {    
  increaseCount() {
    this.count += 1;
  },

  get count() {
    return this._count || 0;
  },

  set count(v) {
    this._count = v;
  }
};

但是你的getter和setter函数不是访问器方法吗? - johny why
你的getCount不是一个访问器方法吗? - johny why
你的Animal类带有getter和setter,使用以下代码会返回0:let b = new Animal; console.log(Animal.count); - johny why
@johnywhy 这是预期的行为。这是一个静态类,应该像这样使用 Animal.increaseCount(); console.log(Animal.count === 1)getCount 是被称为访问器方法的方法,由一对 getset 定义的 count 被称为访问器描述符。 - Estus Flask
1
我认为为了更加实用,你的类应该在创建新实例时自增get/set。你的最终对象样式实际上并没有返回_count的值,如果_count == 0,则返回0,实际上_count未定义。这是一个假结果,我认为这是一个bug。由于该对象需要在外部将计数初始化为0,因此它似乎与使用变量没有任何区别。那么行为与变量无异的对象是否被视为反模式呢? - johny why
1
@johnywhy 感谢您的注意,有一个错别字,应该是在increaseCount和其他成员中使用this.count而不是this._count。get count() { return this._count || 0 }_count: 0, get count() { return this._count }大致相同。我更喜欢使用|| 0,因为它需要更少的字符来输入,并且还可以修复一些无效的值。在set中,可以将其更改为this._count = +v || 0以获得更好的值条件。这取决于情况是否是反模式。对象可以扩展并且更易于测试。如果需要,其行为随时可以更改。 - Estus Flask

4

2
没问题,但是最后一行代码返回的是1而不是undefined。这可能会误导新手。 - Nick
今天你不必再在外面声明它了。 - Eric

2
你可以使用静态getter和setter模拟静态变量。
 export class MyClass {
  static get variable() {
    return this.foo;
  }

  static set variable(foo) {
    this.foo = foo;
  }
}

并像这样使用它

  MyClass.variable = 'bar';
  console.log(MyClass.variable);

1
你可以使用闭包来模拟静态变量。

const Animal= (() => {
    let count= 0;

    class Animal {
        constructor() {}

        static increaseCount() {
            count += 1;
        }

        static getCount() {
            return count;
        }
    }

    return Animal;
})();

console.log(Animal.getCount());
Animal.increaseCount();
console.log(Animal.getCount());


1
要设置静态变量,请在Animal对象本身上设置它。截至目前,在Javascript中,您无法像声明静态方法那样直接在类内声明静态属性。
class Animal {
    constructor() {
    }

    static increaseCount() {
        this.count += 1;
    }

    static getCount() {
        return this.count;
    }
}
Animal.count = 0;
console.log(Animal.increaseCount());
console.log(Animal.getCount()); 

0

如果你想要有递增的ID:

 constructor() {
    super(template);
    if (typeof MyClass.nextId == 'undefined') {
    MyClass.nextId = 0;
    }
    this._id = `${MyClass.nextId++}`;
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接