ES6 Class Basics
JavaScript’s object-oriented inheritance model can confuse developers coming from languages such as C++, C#, Java, and PHP. For this reason, ES6 introduced classes. They are primarily syntactical sugar but offer more familiar object-oriented programming concepts. A class is an object template which defines how objects of that type behave. The followingAnimal
class defines generic animals (classes are normally denoted with an initial capital to distinguish them from objects and other types):
class Animal {
constructor(name = 'anonymous', legs = 4, noise = 'nothing') {
this.type = 'animal';
this.name = name;
this.legs = legs;
this.noise = noise;
}
speak() {
console.log(`${this.name} says "${this.noise}"`);
}
walk() {
console.log(`${this.name} walks on ${this.legs} legs`);
}
}
Class declarations always execute in strict mode. There’s no need to add 'use strict'
.
The constructor method is run when an object of the Animal type is created. It typically sets initial properties and handles other initializations. speak()
and walk()
are instance methods which add further functionality.
An object can now be created from this class with the new
keyword:
let rex = new Animal('Rex', 4, 'woof');
rex.speak(); // Rex says "woof"
rex.noise = 'growl';
rex.speak(); // Rex says "growl"
Getters and Setters
Setters are special methods used to define values only. Similarly, Getters are special methods used to return a value only. For example:class Animal {
constructor(name = 'anonymous', legs = 4, noise = 'nothing') {
this.type = 'animal';
this.name = name;
this.legs = legs;
this.noise = noise;
}
speak() {
console.log(`${this.name} says "${this.noise}"`);
}
walk() {
console.log(`${this.name} walks on ${this.legs} legs`);
}
// setter
set eats(food) {
this.food = food;
}
// getter
get dinner() {
return `${this.name} eats ${this.food || 'nothing'} for dinner.`;
}
}
let rex = new Animal('Rex', 4, 'woof');
rex.eats = 'anything';
console.log( rex.dinner ); // Rex eats anything for dinner.
Child or Sub-classes
It’s often practical to use one class as the base for another. AHuman
class could inherit all the properties and methods from the Animal
class using the extends
keyword. Properties and methods can be added, removed, or changed as necessary so human object creation becomes easier and more readable:
class Human extends Animal {
constructor(name) {
// call the Animal constructor
super(name, 2, 'nothing of interest');
this.type = 'human';
}
// override Animal.speak
speak(to) {
super.speak();
if (to) console.log(`to ${to}`);
}
}
super
refers to the parent class, so it’s usually the first call made in the constructor
. In this example, the Human speak()
method overrides that defined in Animal
.
Object instances of Human
can now be created:
let don = new Human('Don');
don.speak('anyone'); // Don says "nothing of interest" to anyone
don.eats = 'burgers';
console.log( don.dinner ); // Don eats burgers for dinner.
Static Methods and Properties
Defining a method with thestatic
keyword allows it to be called on a class without creating an object instance. Consider the Math.PI
constant: there’s no need to create a Math
object before accessing the PI
property.
ES6 doesn’t support static properties in the same way as other languages, but it is possible to add properties to the class definition itself. For example, the Human
class can be adapted to retain a count of how many human objects have been created:
class Human extends Animal {
constructor(name) {
// call the Animal constructor
super(name, 2, 'nothing of interest');
this.type = 'human';
// update count of Human objects
Human.count++;
}
// override Animal.speak
speak(to) {
super.speak();
if (to) console.log(`to ${to}`);
}
// return number of human objects
static get COUNT() {
return Human.count;
}
}
// static property of the class itself - not its objects
Human.count = 0;
The class’s static COUNT
getter returns the number of humans accordingly:
console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 0
let don = new Human('Don');
console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 1
let kim = new Human('Kim');
console.log(`Humans defined: ${Human.COUNT}`); // Humans defined: 2
ES2019 Class Fields (NEW)
The new class fields implementation allows public properties to initialized at the top of a class outside any constructor:class MyClass {
a = 1;
b = 2;
c = 3;
}
This is equivalent to:
class MyClass {
constructor() {
this.a = 1;
this.b = 2;
this.c = 3;
}
}
If you still require a constructor, initializers will be executed before it runs.
Static Class Fields
In the example above, static properties were inelegantly added to the class definition object after it had been defined. This isn’t necessary with class fields:class MyClass {
x = 1;
y = 2;
static z = 3;
}
console.log( MyClass.z ); // 3
This is equivalent to:
class MyClass {
constructor() {
this.x = 1;
this.y = 2;
}
}
MyClass.z = 3;
console.log( MyClass.z ); // 3
Private Class Fields
All properties in ES6 classes are public by default and can be examined or modified outside the class. In theAnimal
examples above, there’s nothing to prevent the food
property being changed without calling the eats
setter:
class Animal {
constructor(name = 'anonymous', legs = 4, noise = 'nothing') {
this.type = 'animal';
this.name = name;
this.legs = legs;
this.noise = noise;
}
set eats(food) {
this.food = food;
}
get dinner() {
return `${this.name} eats ${this.food || 'nothing'} for dinner.`;
}
}
let rex = new Animal('Rex', 4, 'woof');
rex.eats = 'anything'; // standard setter
rex.food = 'tofu'; // bypass the eats setter altogether
console.log( rex.dinner ); // Rex eats tofu for dinner.
Other languages often permit private
properties to be declared. That’s not possible in ES6, so developers often work around it using the underscore convention (_propertyName
), closures, symbols, or WeakMaps. An underscore provides a hint to the developer, but there’s nothing to prevent them accessing that property.
In ES2019, private class fields are defined using a hash #
prefix:
class MyClass {
a = 1; // .a is public
#b = 2; // .#b is private
static #c = 3; // .#c is private and static
incB() {
this.#b++;
}
}
let m = new MyClass();
m.incB(); // runs OK
m.#b = 0; // error - private property cannot be modified outside class
Note that there’s no way to define private methods, getters, or setters. A TC39 stage 3: draft proposal suggests using a hash #
prefix on names and it has been implemented in Babel. For example:
class MyClass {
// private property
#x = 0;
// private method (can only be called within the class)
#incX() {
this.#x++;
}
// private setter (can only be used within the class)
set #setX(x) {
this.#x = x;
}
// private getter (can only be used within the class)
get #getX() {
return this.$x;
}
}
Immediate Benefit: Cleaner React Code!
React components often have methods tied to DOM events. To ensurethis
resolves to the component, it’s necessary to bind
every method accordingly. For example:
class App extends Component {
constructor() {
super();
this.state = { count: 0 };
// bind all methods
this.incCount = this.incCount.bind(this);
}
incCount() {
this.setState(ps => { count: ps.count + 1 })
}
render() {
return (
<div>
<p>{ this.state.count }</p>
<button onClick={this.incCount}>add one</button>
</div>
);
}
}
When incCount
is defined as an ES2019 class field, it can be assigned as a function using the ES6 =>
fat arrow, which is automatically bound to the defining object. It’s no longer necessary to add bind
declarations:
class App extends Component {
state = { count: 0 };
incCount = () => {
this.setState(ps => { count: ps.count + 1 })
};
render() {
return (
<div>
<p>{ this.state.count }</p>
<button onClick={this.incCount}>add one</button>
</div>
);
}
}
Class Fields: an Improvement?
ES6 class definitions were simplistic. ES2019 class fields require less code, aid readability, and enable some interesting object-oriented programming possibilities. Using#
to denote privacy has received some criticism, primarily because it’s ugly and feels like a hack. Most languages implement a private
keyword, so attempting to use that member outside the class will be rejected by the compiler.
JavaScript is interpreted. Consider the following code:
class MyClass {
private secret = 123;
}
const myObject = new MyClass();
myObject.secret = 'one-two-three';
This would have thrown a runtime error on the last line, but that’s a severe consequence for simply attempting to set a property. JavaScript is purposely forgiving and ES5 permitted property modification on any object.
Although clunky, the #
notation is invalid outside a class definition. Attempting to access myObject.#secret
can throw a syntax error.
The debate will continue but, like them or not, class fields have been adopted in several JavaScript engines. They’re here to stay.
Frequently Asked Questions (FAQs) about JavaScript Private Class Fields
What are the benefits of using private class fields in JavaScript?
Private class fields in JavaScript provide a way to encapsulate or hide data within a class, which is a fundamental principle of object-oriented programming. This encapsulation ensures that the internal state of an object can only be changed in ways that are explicitly defined by the class. This leads to more robust and predictable code, as it prevents external code from inadvertently changing the object’s state in unexpected ways. Additionally, private fields can help to simplify the interface of a class, as they reduce the number of properties and methods that are exposed to the outside world.
How do I declare a private class field in JavaScript?
In JavaScript, private class fields are declared by prefixing the field name with a hash (#) symbol. For example, to declare a private field named ‘value’ within a class, you would write #value
. This field would then be accessible only within methods of the class, and not from outside the class.
Can I access a private class field from outside the class?
No, private class fields in JavaScript are not accessible from outside the class. This is by design, as one of the main purposes of private fields is to hide internal data from the outside world. If you attempt to access a private field from outside the class, JavaScript will throw a syntax error.
Can I use private class fields in conjunction with public fields?
Yes, a JavaScript class can have both private and public fields. Public fields are declared in the same way as private fields, but without the hash (#) prefix. Public fields can be accessed and modified from both inside and outside the class, unlike private fields which can only be accessed from within the class.
How do private class fields differ from private methods in JavaScript?
Private class fields and private methods in JavaScript serve similar purposes, in that they both provide a way to hide internal details of a class from the outside world. However, they are used in different ways. Private fields are used to store data that is only accessible within the class, while private methods are functions that can only be called from within the class.
Can I use private class fields in all JavaScript environments?
Private class fields are a relatively new feature in JavaScript, and as such they are not supported in all environments. As of the time of writing, private fields are supported in the latest versions of most major browsers, including Chrome, Firefox, and Safari. However, they are not supported in Internet Explorer. If you need to write code that works in all browsers, you may need to use a transpiler like Babel to convert your code into a form that older browsers can understand.
How do I use a private class field within a method of the class?
To use a private class field within a method of the class, you simply refer to the field by its name, prefixed with the hash (#) symbol. For example, if you have a private field named ‘value’, you could access it within a method like this: this.#value
.
Can I use private class fields in subclasses?
Yes, private class fields can be used in subclasses in JavaScript. However, each subclass has its own separate set of private fields, which are not shared with the superclass or with other subclasses. This means that if a subclass declares a private field with the same name as a private field in the superclass, the two fields are completely separate and do not interfere with each other.
Can I use private class fields in static methods?
No, private class fields cannot be used in static methods in JavaScript. This is because static methods are associated with the class itself, rather than with instances of the class, and private fields are only accessible within instances of the class.
Can I iterate over private class fields in JavaScript?
No, private class fields in JavaScript are not included in iterations over the properties of an object. This is by design, as one of the main purposes of private fields is to hide internal data from the outside world. If you need to iterate over the properties of an object, you should use public fields or methods instead.
Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.