5.4 Classes

5.4.1 Constructors

Constructors are optional. Subclass constructors must call super() beforesetting any fields or otherwise accessing this. Interfaces should declarenon-method properties in the constructor.

5.4.2 Fields

Set all of a concrete object’s fields (i.e. all properties other than methods)in the constructor. Annotate fields that are never reassigned with @const(these need not be deeply immutable). Annotate non-public fields with the propervisibility annotation (@private, @protected, @package), and end all@private fields' names with an underscore. Fields are never set on a concreteclass' prototype.

Example:

  1. class Foo {
  2. constructor() {
  3. /** @private @const {!Bar} */
  4. this.bar_ = computeBar();
  5. /** @protected @const {!Baz} */
  6. this.baz = computeBaz();
  7. }
  8. }

Tip: Properties should never be added to or removed from an instance after theconstructor is finished, since it significantly hinders VMs’ ability tooptimize. If necessary, fields that are initialized later should be explicitlyset to undefined in the constructor to prevent later shape changes. Adding@struct to an object will check that undeclared properties are notadded/accessed. Classes have this added by default.

5.4.3 Computed properties

Computed properties may only be used in classes when the property is asymbol. Dict-style properties (that is, quoted or computed non-symbol keys, asdefined in ??) are not allowed. A[Symbol.iterator] method should be defined for any classes that are logicallyiterable. Beyond this, Symbol should be used sparingly.

Tip: be careful of using any other built-in symbols (e.g., Symbol.isConcatSpreadable) as they are not polyfilled by the compiler and will therefore not work in older browsers.

5.4.4 Static methods

Where it does not interfere with readability, prefer module-local functions overprivate static methods.

Static methods should only be called on the base class itself. Static methodsshould not be called on variables containing a dynamic instance that may beeither the constructor or a subclass constructor (and must be defined with@nocollapse if this is done), and must not be called directly on a subclassthat doesn’t define the method itself.

Disallowed:

  1. class Base { /** @nocollapse */ static foo() {} }
  2. class Sub extends Base {}
  3. function callFoo(cls) { cls.foo(); } // discouraged: don't call static methods dynamically
  4. Sub.foo(); // Disallowed: don't call static methods on subclasses that don't define it themselves

5.4.5 Old-style class declarations

While ES6 classes are preferred, there are cases where ES6 classes may not befeasible. For example:

  • If there exist or will exist subclasses, including frameworks that createsubclasses, that cannot be immediately changed to use ES6 class syntax. Ifsuch a class were to use ES6 syntax, all downstream subclasses not using ES6class syntax would need to be modified.

  • Frameworks that require a known this value before calling the superclassconstructor, since constructors with ES6 super classes do not haveaccess to the instance this value until the call to super returns.

In all other ways the style guide still applies to this code: let, const,default parameters, rest, and arrow functions should all be used whenappropriate.

goog.defineClass allows for a class-like definition similar to ES6 classsyntax:

  1. let C = goog.defineClass(S, {
  2. /**
  3. * @param {string} value
  4. */
  5. constructor(value) {
  6. S.call(this, 2);
  7. /** @const */
  8. this.prop = value;
  9. },
  10. /**
  11. * @param {string} param
  12. * @return {number}
  13. */
  14. method(param) {
  15. return 0;
  16. },
  17. });

Alternatively, while goog.defineClass should be preferred for all new code,more traditional syntax is also allowed.

  1. /**
  2. * @constructor @extends {S}
  3. * @param {string} value
  4. */
  5. function C(value) {
  6. S.call(this, 2);
  7. /** @const */
  8. this.prop = value;
  9. }
  10. goog.inherits(C, S);
  11. /**
  12. * @param {string} param
  13. * @return {number}
  14. */
  15. C.prototype.method = function(param) {
  16. return 0;
  17. };

Per-instance properties should be defined in the constructor after the call to the super class constructor, if there is a super class. Methods should be defined on the prototype of the constructor.

Defining constructor prototype hierarchies correctly is harder than it first appears! For that reason, it is best to use goog.inherits from the Closure Library .

5.4.6 Do not manipulate prototypes directly

The class keyword allows clearer and more readable class definitions thandefining prototype properties. Ordinary implementation code has no businessmanipulating these objects, though they are still useful for defining classes asdefined in ??. Mixins and modifying theprototypes of builtin objects are explicitly forbidden.

Exception: Framework code (such as Polymer, or Angular) may need to use prototypes, and should notresort to even-worse workarounds to avoid doing so.

5.4.7 Getters and Setters

Do not use JavaScript getter and setter properties. They are potentiallysurprising and difficult to reason about, and have limited support in thecompiler. Provide ordinary methods instead.

Exception: there are situations where defining a getter or setter isunavoidable (e.g. data binding frameworks such as Angular and Polymer, or forcompatibility with external APIs that cannot be adjusted). In these cases only,getters and setters may be used with caution, provided they are defined withthe get and set shorthand method keywords or Object.defineProperties (notObject.defineProperty, which interferes with property renaming). Gettersmust not change observable state.

Disallowed:

  1. class Foo {
  2. get next() { return this.nextId++; }
  3. }

5.4.8 Overriding toString

The toString method may be overridden, but must always succeed and never havevisible side effects.

Tip: Beware, in particular, of calling other methods from toString, sinceexceptional conditions could lead to infinite loops.

5.4.9 Interfaces

Interfaces may be declared with @interface or @record. Interfaces declaredwith @record can be explicitly (i.e. via @implements) or implicitlyimplemented by a class or object literal.

All non-static method bodies on an interface must be empty blocks. Fields mustbe declared as uninitialized members in the class constructor.

Example:

  1. /**
  2. * Something that can frobnicate.
  3. * @record
  4. */
  5. class Frobnicator {
  6. constructor() {
  7. /** @type {number} The number of attempts before giving up. */
  8. this.attempts;
  9. }
  10. /**
  11. * Performs the frobnication according to the given strategy.
  12. * @param {!FrobnicationStrategy} strategy
  13. */
  14. frobnicate(strategy) {}
  15. }

5.4.10 Abstract Classes

Use abstract classes when appropriate. Abstract classes and methods must beannotated with @abstract. Do not use goog.abstractMethod. See abstractclasses and methods.