Using ES6 Classes in OpenSphere¶
As part of the transition, OpenSphere is converting all Google Closure style classes to use the ES6 class syntax. An ES6 class
is fundamentally the same as a class in Closure, which means the two can be intermingled to a degree.
External Documentation¶
The following are a few helpful guides/references for ES6 classes. It’s recommended to start here if you do not already have an understanding of JavaScript classes.
ES6 vs Closure¶
While ES6 classes and Closure classes are nearly the same under the hood, they are very different in syntax. This section outlines the key differences.
Closure Constructor¶
- Class name assigned to a constructor function.
- Requires the
@constructor
JSDoc annotation to inform the compiler that it’s a class constructor. - If it extends another class, the
@extends
annotation is also required. - Parent constructor is invoked with
<class>.base(this, 'constructor', <args>)
. - Class properties are added with
this.<property>
. - Prototypal inheritance is established by calling
goog.inherits(<child>, <parent>)
.
/**
* Description of the class.
* @param {string} arg1 A string arg.
* @param {number} arg2 A number arg.
* @extends {os.MyParentClass}
* @constructor
*/
os.MyClass = function(arg1, arg2) {
os.MyClass.base(this, 'constructor', arg1);
/**
* Description of arg2.
* @type {number}
* @private
*/
this.arg2_ = arg2;
};
goog.inherits(os.MyClass, os.MyParentClass);
ES6 Constructor¶
- Defined using the
class
keyword. - Constructor is defined with the special
constructor
function. The@constructor
annotation is not required. - If it extends another class, the
extends
keyword is used. This establishes prototypal inheritance, and cues the compiler so@extends
is not required.@extends
is still used when providing generic types for a template (ie,@extends {MyParentClass<MyType>}
). - Parent constructor is invoked with
super(<args>)
. - Class properties are added with
this.<property>
.
/**
* Description of the class.
*/
class MyClass extends MyParentClass {
/**
* @param {string} arg1 A string arg.
* @param {number} arg2 A number arg.
*/
constructor(arg1, arg2) {
super(arg1);
/**
* Description of arg2.
* @type {number}
* @private
*/
this.arg2_ = arg2;
}
/**
* @inheritDoc
*/
setArg1(arg1) {
super.setArg1(arg1);
console.log('Set arg1 in parent.');
}
}
Warning
An ES6 class can extend a Closure class, but not the reverse. Closure classes add properties under the hood for goog.base
and ES6 classes will not have these. This means leaf classes must be refactored before their parents.
Member Functions¶
With Closure classes, member functions are added to the prototype. They reference the parent function using <class>.base(this, '<function>', <args>)
.
/**
* @inheritDoc
*/
os.MyClass.prototype.setArg1 = function(arg1) {
os.MyClass.base(this, 'setArg1', arg1);
console.log('Set arg1 in parent.');
};
In ES6, member functions are defined within the class
. They reference the parent function using super.<function>(<args>)
.
/**
* @inheritDoc
*/
setArg1(arg1) {
super.setArg1(arg1);
console.log('Set arg1 in parent.');
}
Properties¶
Both class styles define their properties in the constructor in much the same manner.
The key difference for ES6 classes is that they are @struct by default, which prevents using bracket notation or adding properties outside the constructor. If either of those are needed (ie, using bracket notation to avoid property renaming), the class must have @unrestricted in the JSDoc.
/**
* @unrestricted
*/
class MyClass {
constructor() {
// bracket notation to avoid compilation
this['id'] = 1234;
}
}
Singletons¶
Class instance singletons have historically been created using goog.addSingletonGetter
which adds a getInstance
function to the class. With ES6 classes and the local scope provided by modules, this is easy to do natively by adding a static getInstance()
call to the class.
// store the instance in a module-scoped variable that can be externally referenced
// with MyClass.getInstance()
let instance;
class MyClass {
constructor() {}
static getInstance() {
// do not create the instance until the first time this function is called
if (!instance) {
instance = new MyClass();
}
return instance;
}
}
Constants¶
Constants on a class can be represented using a combination of the static and get keywords. This is a convenient way to define the constant on the class without needing to export the constant.
class MyClass {
constructor() {}
static get MY_CONSTANT() {
return theConstant;
}
}
// constant can be externally referenced with MyClass.MY_CONSTANT
const theConstant = 42;