Key Takeaways
- JavaScript classes provide constructor and inheritance syntax built on prototypes; they don't replace JavaScript's object model.
- Constructor assignments and public instance fields create per-instance properties, while public instance methods are shared through the class's prototype.
- A derived constructor must call
super()before accessingthis. - Public class fields and private elements were standardized in ES2022, not ES6 or ES2026.
JavaScript classes let you group instance data, initialization, and behavior using familiar object-oriented syntax. Underneath, a class is a special constructor function, and its public instance methods use JavaScript's prototype-based inheritance.
This introduction covers constructors, properties, methods, inheritance, and modern private elements. If the surrounding syntax is unfamiliar, start with our JavaScript ES6 introduction.
JavaScript ES6 Classes: A First Example
Start with an animal that has a name, a species, and one behavior:
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
const dog = new Animal('Charlie', 'Dog');
dog.speak(); // Charlie makes a noise.The new Animal(...) expression creates an instance and runs its constructor. Inside that constructor, this refers to the new instance, so the assignments create its name and species properties.
The examples below can run in order in one JavaScript file. Blocks marked as fragments continue an earlier example rather than redefining its class.
How Classes Work With Prototypes
A prototype is another object that JavaScript consults when a property isn't found directly on an object. In this example, dog owns its data, but speak lives on Animal.prototype.
dog
own properties: name, species
|
v
Animal.prototype
shared method: speak
|
v
Object.prototype
|
v
nullYou can inspect that relationship. Object.getPrototypeOf() returns an object's prototype, and Object.hasOwn() checks whether a property belongs directly to that object.
// Fragment: continues the Animal example.
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true
console.log(Object.hasOwn(dog, 'name')); // true
console.log(Object.hasOwn(dog, 'speak')); // false
console.log(dog.speak === Animal.prototype.speak); // truePublic methods declared in a class are also non-enumerable: they don't appear in for...in enumeration. Class syntax makes common constructor-and-prototype patterns easier to express, but the prototype model remains important.
JavaScript Class Constructor
The constructor initializes an instance from its arguments. Here's one Person declaration containing both of the methods we'll use later:
class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
greet() {
return `Hello, ${this.getFullName()}!`;
}
getFullName() {
return `${this.first} ${this.last}`;
}
}
const user = new Person('John', 'Doe');
console.log(user.greet()); // Hello, John Doe!Use constructors for required inputs and initialization logic. If you omit a constructor, JavaScript supplies a default: an empty constructor for a base class, or one that forwards arguments to the parent constructor for a derived class.
Class Declaration Rules
- Use
new. Calling a class as an ordinary function throws aTypeError. - Declare it before using it. Unlike a function declaration, a class declaration cannot be used before execution reaches it. Its binding is in the temporal dead zone—the period before initialization when access throws a
ReferenceError. - Expect strict mode. Class bodies run in strict mode even without a
"use strict"directive.
ES6 Class Methods
Methods define behavior using the instance's data. Because getFullName() is already part of Person, you can create another person without declaring the class again:
// Fragment: continues the Person example.
const person = new Person('Alice', 'Smith');
console.log(person.getFullName()); // Alice Smith
console.log(person.getFullName === user.getFullName); // trueThe two instances share the same method function through Person.prototype, but calling it through each instance supplies a different this. You don't need to copy a method onto every object to make it operate on different data.
Class Properties vs. Public Class Fields
this.first = first is an assignment executed by the constructor. A declaration such as first = '' inside the class body is a public class field—a later feature with its own initialization rules. Both can provide ordinary public instance properties.
class Member {
first = '';
last = '';
nickname;
tags = [];
constructor(first, last) {
this.first = first;
this.last = last;
}
}
const memberA = new Member('Ada', 'Lovelace');
const memberB = new Member('Grace', 'Hopper');
console.log(memberA.first); // Ada
console.log(memberA.nickname); // undefined
console.log(memberA.tags === memberB.tags); // falseHere, first and last begin as empty strings before the constructor overwrites them. A field without an initializer starts as undefined, and the [] initializer creates a separate array for each instance.
The important initialization differences are:
- Base-class instance fields initialize before the constructor body runs.
- Derived-class instance fields initialize after the parent constructor runs, as the
super()call completes. - A public field declaration defines an own data property; unlike assignment, it doesn't invoke an inherited setter with the same name.
Use fields to make instance state and defaults visible at the top of the class. Use constructor assignments for values that depend on constructor arguments. Our guide to JavaScript objects provides more background on properties.
Where Class Members Live
| Definition | Location | Typical access |
|---|---|---|
Constructor assignment: this.first = first | Instance, in this example | person.first |
Public instance field: first = '' | Instance | memberA.first |
Public instance method: getFullName() {} | Class prototype | person.getFullName() |
| Static field or method | Class constructor itself | Point.dimensions or Point.origin() |
This distinction separates per-object state from shared behavior and class-level utilities.
JavaScript Class Inheritance With extends and super()
Use extends to derive a class from another constructor. The subclass can inherit public methods, add behavior, and override methods with its own implementations.
// Fragment: run after the Person declaration above.
class FormalPerson extends Person {
constructor(first, last, title) {
super(first, last);
this.title = title;
}
getFormalName() {
return `${this.title} ${this.first} ${this.last}`;
}
greet() {
return `${super.greet()} (${this.title})`;
}
}
const formalPerson = new FormalPerson('Alan', 'Turing', 'Dr.');
console.log(formalPerson.getFormalName()); // Dr. Alan Turing
console.log(formalPerson.getFullName()); // Alan Turing
console.log(formalPerson.greet()); // Hello, Alan Turing! (Dr.)super(first, last) invokes the parent constructor, which initializes first and last on the instance. Accessing this before that call in a derived constructor throws a ReferenceError. Separately, super.greet() calls the parent method while retaining the current instance as this.
Choose inheritance when the child genuinely fits the parent's contract. For unrelated capabilities, prefer composition: give an object a collaborator, such as a formatter or logger, rather than making it inherit from one. Sharing a few methods isn't, by itself, a good reason to create a class hierarchy.
Static Methods and Static Class Fields
Static members belong to the class constructor, not its instances. Static methods are useful for factory functions and utilities associated with a class; static fields hold class-level data.
class Point {
static dimensions = 2;
constructor(x, y) {
this.x = x;
this.y = y;
}
static origin() {
return new Point(0, 0);
}
}
const origin = Point.origin();
console.log(Point.dimensions); // 2
console.log(origin.x); // 0
console.log(origin.dimensions); // undefinedStatic field initializers run when the class is evaluated, rather than once per instance. Remember that the method syntax is part of ES6, while the static dimensions = 2 field syntax is part of ES2022.
JavaScript Private Fields and Private Methods
A private field stores internal state; a private method implements internal behavior. Declare both using a # name. Private fields must be declared in the class body before you assign to them.
class SecureDocument {
#content;
static #processed = 0;
constructor(content) {
this.#content = content;
}
#validate() {
return typeof this.#content === 'string'
&& this.#content.length > 0;
}
process() {
if (!this.#validate()) {
throw new Error('Document must contain text');
}
SecureDocument.#processed += 1;
console.log('Document is valid');
}
static processedCount() {
return SecureDocument.#processed;
}
}
const doc = new SecureDocument('Meeting notes');
doc.process(); // Document is valid
console.log(SecureDocument.processedCount()); // 1
// doc.#validate(); // SyntaxError if uncommented outside the class.JavaScript enforces this privacy. Private elements aren't ordinary string-keyed or symbol-keyed properties, so normal property lookup and reflection don't expose them. For example, doc['#content'] doesn't read the private field.
Private names are lexically scoped: their meaning comes from the class declaration containing the code. A subclass cannot directly name its parent's private elements; inherited public methods can still operate on them.
Uncommenting doc.#validate() in an ordinary JavaScript file causes a parse-time SyntaxError, not a failed runtime permission check. Some developer consoles relax this restriction for debugging, so use a script file to check the language rule.
Private Static Elements
The example's static #processed belongs to SecureDocument, not to each document. Private static methods use the same combination, such as static #helper() {}. Access private static elements through their declaring class name; using this can fail when an inherited static method is called on a subclass.
Getters and Setters
A getter runs when you read a property, and a setter runs when you assign to it. They let you keep property-style access while validating writes or computing values.
class Nickname {
#value;
constructor(value) {
this.value = value;
}
get value() {
return this.#value;
}
set value(value) {
if (typeof value !== 'string') {
throw new TypeError('Nickname must be a string');
}
this.#value = value;
}
}
const nickname = new Nickname('Charlie');
nickname.value = 'Chuck';
console.log(nickname.value); // ChuckRead nickname.value, not nickname.value(). The setter writes to a separate backing field, #value; assigning to this.value inside that setter would call the setter again.
Common Mistakes
- Declaring the same class twice in one scope. Two
class Persondeclarations in the same scope are a syntax error. Extend the existing example or give independent examples different names. - Losing the method receiver. With
const greet = user.greet; greet();, the method'sthisisundefined. Useuser.greet(), a wrapper such as() => user.greet(), oruser.greet.bind(user)when passing a callback. - Treating an underscore as privacy. A name such as
_contentis an ordinary public property, not a language-enforced private element.
If callback behavior is surprising, review JavaScript functions before adding more class machinery.
FAQ
Are JavaScript classes just syntactic sugar over prototypes?
Classes build on prototypes: public instance methods live on the class prototype and are shared by instances. But classes also enforce specific rules, including strict-mode bodies, construction with new, and declaration initialization restrictions, so they aren't interchangeable with every constructor-function pattern.
What is the difference between class fields and constructor properties?
A public instance field is declared in the class body and initialized automatically for each instance. Constructor assignment runs when execution reaches that statement; unlike a field declaration, it can invoke an inherited setter.
How do you declare a private field in JavaScript?
Declare a hash-prefixed name in the class body, such as #value = 0, and access it as this.#value from code in the class. Private fields store state and were standardized in ES2022, not ES6.
How do you create a private method in JavaScript?
Declare a hash-prefixed method, such as #validate() {}, inside the class and call it there using this.#validate(). Private methods were standardized in ES2022 and don't require TypeScript; referencing their private names outside the declaring class's private scope is a syntax error.
When must a derived class call super()?
A derived constructor must call super() before accessing this. If you don't declare a constructor in the subclass, JavaScript supplies one that forwards its arguments to the parent constructor.
Further Reading
- MDN: Classes — syntax, strict mode, and member evaluation.
- MDN: Method definitions — prototype placement and method semantics.
- MDN: Public class fields — initialization order and differences from assignment.
- MDN: constructor — initialization, default constructors, and super().
- MDN: extends — inheritance behavior and composition trade-offs.
- MDN: static — class-level fields, methods, and accessors.
- MDN: Private elements — private fields, methods, and static access restrictions.
- ECMAScript 2022 language specification — the standard that added class fields and private elements.
