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:
class Foo {
constructor() {
/** @private @const {!Bar} */
this.bar_ = computeBar();
/** @protected @const {!Baz} */
this.baz = computeBaz();
}
}
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:
class Base { /** @nocollapse */ static foo() {} }
class Sub extends Base {}
function callFoo(cls) { cls.foo(); } // discouraged: don't call static methods dynamically
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 instancethis
value until the call tosuper
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:
let C = goog.defineClass(S, {
/**
* @param {string} value
*/
constructor(value) {
S.call(this, 2);
/** @const */
this.prop = value;
},
/**
* @param {string} param
* @return {number}
*/
method(param) {
return 0;
},
});
Alternatively, while goog.defineClass
should be preferred for all new code,more traditional syntax is also allowed.
/**
* @constructor @extends {S}
* @param {string} value
*/
function C(value) {
S.call(this, 2);
/** @const */
this.prop = value;
}
goog.inherits(C, S);
/**
* @param {string} param
* @return {number}
*/
C.prototype.method = function(param) {
return 0;
};
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 prototype
s, 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:
class Foo {
get next() { return this.nextId++; }
}
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:
/**
* Something that can frobnicate.
* @record
*/
class Frobnicator {
constructor() {
/** @type {number} The number of attempts before giving up. */
this.attempts;
}
/**
* Performs the frobnication according to the given strategy.
* @param {!FrobnicationStrategy} strategy
*/
frobnicate(strategy) {}
}
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.