class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
- The new private fields syntax is similar to public fields, except
we mark the field as being private by using #
. We can think of the#
as being part of the field name:
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
- Private fields are not accessible outside of the class body:
const counter = new IncreasingCounter();
counter.#count;
// → SyntaxError
counter.#count = 42;
// → SyntaxError
- Class fields syntax can be used to create public and private static properties and methods:
class FakeMath {
// `PI` is a static public property.
static PI = 22 / 7; // Close enough.
// `#totallyRandomNumber` is a static private property.
static #totallyRandomNumber = 4;
// `#computeRandomNumber` is a static private method.
static #computeRandomNumber() {
return FakeMath.#totallyRandomNumber;
}
// `random` is a static public method (ES2015 syntax)
// that consumes `#computeRandomNumber`.
static random() {
console.log('I heard you like random numbers…');
return FakeMath.#computeRandomNumber();
}
}
FakeMath.PI;
// → 3.142857142857143
FakeMath.random();
// logs 'I heard you like random numbers…'
// → 4
FakeMath.#totallyRandomNumber;
// → SyntaxError
FakeMath.#computeRandomNumber();
// → SyntaxError
- First: Using a Function Approach
function SponsorWidget(name, description, url) {
// Constructor functions are invoked with
this.name = name; // the 'new' operator
this.description = description;
this.url = ulr;
}
SponsorWidget.prototype.render = function () {
//...
};
// Invoking the SponsorWidget function looks like this:
let sponsorWidget = new SponsorWidget(name, description, url);
sponsorWidget.render();
- Second: Using the New Class Syntax (Object Oriented Flavour like other OOP language)
- To define a class, we use the class keyword followed by the name of the class. The body of a class is the part between curly braces.
class SponsorWidget {
constructor(name, description, url) {
//...
this.description = description; // don't forget to use 'this' to access instance poperties and methods
this.url = url;
}
render() {
let link = this._buildLink(this.url);
// ^ can access previously assigned instance variables
}
_buildLink(url) {
// Prefixing a method with an underscore is a convention for indicating that it should not be invoked from the public API (Private Method)
}
}
- The class syntax in not introducing a new object model to JavaScript. It's just syntactical sugar over the existing prototype-based inheritance.
// syntactic Sugar | // Prototype Object Model
class SponsorWidget { | function SponsorWidget(name, description, url) {
//... | //...
} | }
// Instances are created the same way
let sponsorWidget = new SponsorWidget(name, description, url);
SponsorWidget.render();
- We can use class inheritance to reduce code repetition. Child classes inherit and specialize behavior defined in parent classes.
- The extends keyword is used to create a class that inherits methods and properties from another class. The super method runs the constructor function from parent class.
// parent class | // child class
class Widget { | class SponsorWidget extends Widget {
constructor() { | constructor(name, description, url) {
this.baseCSS = "site-widget"; | super();
} | //...
parse(value) { | }
//... | render(){
} | let parseName = this.parse(this.name); // this.parse is inherited method
} | let css = this._buildCSS(this.baseCSS); // this.baseCSS is inherited properties
| }
| }
- Child classes can invoke methods from their parent classes via the super object
// parent class | // child class
class Widget { | class SponsorWidget extends Widget {
constructor(){ | constructor() {
this.baseCSS = "site-eidget"; | super()
} | //...
} | }
parse() { |
//... | parse(){ // method overriding
} | let parsedName = super.parse(this.name);
} | // ^ calls the parent version of the parse() method
| return `Spnsor: ${parsedName}`;
| }
|
| render() {
| //...
| }
class A {
one() { console.log("one:A"); }
two() { console.log("two:A"); }
}
var B = {
one() { console.log("one:B"); }
one() { console.log("two:B"); }
}
class C extends A {
foo() {
this.one();
super.two();
}
}
var x = new C();
x.foo(); // one:A two:A <--- ok
x.foo.call(B); // one:B two:A <--- Oops!!
class Foo {
constructor(who) {
this.me = who;
}
identify() {
return 'I am ' + this.me;
}
}
class Bar extends Foo {
constructor(who) {
this.x = 1; // <-- error!
super(who); // <-- this must come first
}
}
The execution of instance public fields roughly follows these two
rules:
- In base classes (classes without superclasses), instance public fields are executed
immediately
before the constructor - In derived classes (classes with superclasses):
- The superclass sets up its instance slots when
super()
is called - Instance public fields are executed immediately after
super()
- The superclass sets up its instance slots when
The following example demonstrates the rules:
class SuperClass {
superProp = console.log('superProp');
constructor() {
console.log('super-constructor');
}
}
class SubClass extends SuperClass {
subProp = console.log('subProp');
constructor() {
console.log('BEFORE super()');
super();
console.log('AFTER super()');
}
}
new SubClass();
Output:
BEFORE super()
superProp
super-constructor
subProp
AFTER super()