JavaScript introduced the class keyword in 2015 with the release of ES6. React made classes an indispensable language feature when they introduced support for using extends React.Component instead of React.createClass() in 2015, and removed support for React.createClass() entirely in 2017 in favor of classes. Today, classes are a fundamental part of JavaScript, and many new JavaScript devs don't remember a time before classes. In this article, I'll provide an overview of how classes work in JavaScript: how to declare classes, what features JavaScript provides with classes, and how inheritance works.

Working With Classes

Here's how you define a basic class MyClass , and create an instance of MyClass .

class MyClass { constructor () { this .answer = 42 ; } } const obj = new MyClass(); obj.answer;

You must instantiate a class with new . Calling MyClass() without new throws an error:

MyClass();

A class is technically a function, although the ECMAScript spec explicitly disallows calling a class without new . In fact, the typeof operator identifies MyClass as a function.

typeof MyClass; MyClass instanceof Function ;

To check whether an object is an instance of a class, you should use the instanceof operator. You can also check whether the constructor property is equal to MyClass .

obj instanceof MyClass; obj.constructor === MyClass; const obj = {}; obj.constructor = MyClass; obj instanceof MyClass; obj.constructor === MyClass;

Like functions, classes in JavaScript are variables like any other. You can assign a class to a variable, overwrite that class, and pass a class as a parameter to a function. Like functions, you can also declare classes with or without explicit names.

let Foo = class { constructor () { this .answer = 42 ; } } Foo = class { constructor () { this .answer = 43 ; } } console .log(Foo); console .log( new Foo());

Unlike functions, classes are never hoisted. In the below example the function Foo() prints successfully, because JavaScript looks ahead and 'hoists' Foo() to the top of the function. But trying to print the class Bar throws a reference error, because JavaScript does not hoist class definitions.

Statics, Methods, Getters, Setters

ES6 classes support numerous object-oriented programming constructs, like static functions, instance methods, and getters and setters.

Static functions are functions defined on the class itself. You call ClassName.staticName() , and, within the static function, this refers to the class.

class MyClass { static myStatic() { this ; return 42 ; } } MyClass.myStatic();

Instance methods are functions on instances of the class. When you create a new object using new MyClass() , you can call obj.myMethod() . Within myMethod() , this refers to obj .

class MyClass { constructor () { this .answer = 42 ; } myMethod() { this === obj; return this .answer; } } const obj = new MyClass(); obj.myMethod();

Getters and setters let you define functions that run when you access or assign a property on an instance of the class. For example, getters and setters can let you convert values to numbers when you set the property.

In the below example, instances of MyClass have a special property num that the class tries to convert to a number. The num property has a getter function that JavaScript executes when you access obj.num , and a setter function that JavaScript executes when you assign to obj.num using = or Object.assign() . The setter function converts num to a number, and throws an error if it could not convert the given value to a number.

class MyClass { get num() { return this ._num; } set num(v) { const parsed = parseFloat (v); if ( Number .isNaN(parsed)) { throw new Error ( `" ${v} " is not a number` ); } this ._num = parsed; } } const obj = new MyClass(); obj.num; obj.num = '42' ; obj.num; obj.num = 'not a number' ; Object .assign(obj, { num: 'not a number' });

Inheritance

Inheritance is one of the four core concepts of object-oriented programming. Besides syntactic sugar, the big advantage of using ES6 classes over pre-ES6 functions as class definitions is cleaner inheritance.

JavaScript class inheritance is still prototype-based under the hood, but extends abstracts away prototypes. Without having to write prototype , class inheritance in JavaScript looks a lot like inheritance in more "proper" object-oriented languages like Java. Here's a basic example of inheritance using ES6 classes:

class BaseClass { static foo() { return 1 ; } static bar() { return 2 ; } a() { return 3 ; } b() { return 4 ; } } class ChildClass extends BaseClass { static bar() { return super .bar() * 2 ; } b() { return super .b() * 2 ; } } ChildClass.foo(); ChildClass.bar(); const obj = new ChildClass(); obj.a(); obj.b(); obj instanceof ChildClass; obj instanceof BaseClass;

The class A extends B syntax means the child class A has the same members (including statics, methods, getters, and setters) as the base class B , but can also override B 's members. Here's how you would do the same thing using pre-ES6 prototype-based inheritance.

function BaseClass ( ) {} BaseClass.foo = () => 1 ; BaseClass.bar = () => 2 ; BaseClass.prototype = Object .create( Object .prototype); BaseClass.prototype.a = () => 3 ; BaseClass.prototype.b = () => 4 ; function ChildClass ( ) {} Object .assign(ChildClass, BaseClass); ChildClass.prototype = Object .create(BaseClass.prototype); ChildClass.bar = () => BaseClass.bar() * 2 ; ChildClass.prototype.b = function ( ) { return BaseClass.prototype.b.call( this ) * 2 ; };

Like in Java, you call the parent class's constructor using super , and you need to call super() in the constructor before accessing this .

class BaseClass { constructor () { this .answer = 42 ; } } class Child1 extends BaseClass { constructor () { super (); ++ this .answer; } } class Child2 extends BaseClass { constructor () { this .answer = 43 ; super (); } } new Child1(); new Child2();

Differences vs Node.js util.inherits()

Node.js has a native inherits() function that many developers used for inheritance before ES6. The key difference between inherits() and extends is that Node.js inherits() does not inherit statics.

const util = require ( 'util' ); class BaseClass { static foo() { return 1 ; } bar() { return 2 ; } } class Child1 extends BaseClass {} function Child2 ( ) {} util.inherits(Child2, BaseClass); new Child1().bar(); new Child2().bar(); Child1.foo(); Child2.foo();

Moving On