# JavaScript Classes: Constructor, Fields, and #Private

Source: https://devdocs10.com/articles/javascript/js-classes  
By Murat Bo · Published 2026-07-11 · DevDocs10 (https://devdocs10.com)

JavaScript classes give you a cleaner way to define reusable object blueprints. In this article, you'll learn how class constructors work, how public fields are initialized, and how `#private` fields protect data inside an instance.

> **Quick answer:** A JavaScript class is syntax for creating constructor functions and methods in a more readable way. Use the `constructor` to initialize each instance, public fields for values you want to expose, and `#private` fields for data that should not be accessed from outside the class.

> **Difficulty:** Beginner
>
> **You'll understand this better if you know:** how objects work, how functions receive arguments, and the difference between instance data and shared methods.

## 1. What Is JavaScript Classes: Constructor, Fields, and #Private?

A JavaScript class is a special kind of function syntax for creating objects with shared behavior. It lets you define:

- a `constructor` for one-time setup when a new object is created,
- public fields for data stored on each instance,
- methods for behavior shared by all instances, and
- `#private` fields for internal state that only class code can access.

Classes do not replace objects or prototypes. They provide a clearer way to work with them. Under the hood, a class still uses JavaScript's prototype system.

This topic matters because classes are common in modern JavaScript codebases, especially when modeling things like users, carts, timers, API clients, and UI state managers.

## 2. Why JavaScript Classes: Constructor, Fields, and #Private Matter

Classes help you organize related data and behavior in one place. Instead of passing the same values into many separate functions, you can create an object once and let its methods operate on its own state.

They are especially useful when you need:

- multiple objects with the same shape and behavior,
- clear initialization logic,
- state that should not be changed directly, and
- code that is easier to read than prototype-based constructor functions.

Use classes when your code naturally models stateful things. Do not use them just because they look modern. For simple data transformations or one-off utilities, plain functions may be better.

## 3. Basic Syntax

A class typically contains a constructor, fields, and methods. The constructor runs when you create a new instance with `new`.

### Minimal class example

Here is the smallest useful shape of a class with one field and one method.

```javascript
class Counter {
  count = 0;

  constructor() {
    // Runs when a new instance is created
  }

  increment() {
    this.count += 1;
  }
}

const counter = new Counter();
counter.increment();
```

In this example, `count` is a public field, `constructor` is the setup step, and `increment()` is a method that changes instance state.

### How the pieces fit together

- `class Counter` defines the blueprint.
- `new Counter()` creates an object from that blueprint.
- `this.count` refers to the current instance.
- `increment()` is stored on the prototype, so every instance shares one copy of the method.

## 4. Step-by-Step Examples

### Example 1: Passing values into the constructor

Use the constructor to accept values that every instance needs. This is the most common pattern for initializing class state.

```javascript
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  describe() {
    return `${this.name} <${this.email}>`;
  }
}

const user = new User("Ava", "ava@example.com");
```

This pattern is useful when each object needs its own initial data. The constructor copies the arguments onto the instance so methods can use them later.

### Example 2: Public class fields with default values

Public fields are declared directly in the class body. They are created for each instance before the constructor finishes.

```javascript
class Timer {
  seconds = 0;
  running = false;

  start() {
    this.running = true;
  }
}
```

Use fields like this when a property always exists and should begin with a default value. It makes the class easier to read because the shape of the object is visible immediately.

### Example 3: #private fields for internal state

Fields that start with `#` are private. They are only accessible inside the class body.

```javascript
class BankAccount {
  #balance = 0;

  deposit(amount) {
    this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount();
account.deposit(50);
```

Outside the class, `account.#balance` is not allowed. That makes private fields a strong choice for internal data that must be protected from accidental access.

### Example 4: Constructor with validation

Constructors can validate input before storing it. This helps catch invalid state as early as possible.

```javascript
class Product {
  constructor(name, price) {
    if (typeof name !== "string") {
      throw new TypeError("name must be a string");
    }

    if (typeof price !== "number" || price <= 0) {
      throw new RangeError("price must be a positive number");
    }

    this.name = name;
    this.price = price;
  }
}
```

This pattern is helpful when a class should never exist in an invalid state. The constructor becomes the gatekeeper for acceptable values.

## 5. Practical Use Cases

- **API clients:** store base URLs, authentication state, and helper methods in one object.
- **UI widgets:** keep DOM references, internal counters, and event logic together.
- **Game objects:** model position, velocity, health, and behavior.
- **Form helpers:** manage validation state and derived values.
- **Business entities:** represent users, invoices, carts, or subscriptions.

Classes are a good fit when the object needs both data and behavior over time. They are less useful for simple data formatting or single-purpose helper logic.

## 6. Common Mistakes

### Mistake 1: Forgetting to use `new`

Classes must be instantiated with `new`. Calling a class like a normal function causes a runtime error.

**Problem:** The class constructor is not being called as a constructor, so JavaScript rejects the call.

```javascript
class User {
  constructor(name) {
    this.name = name;
  }
}

const user = User("Ava");
```

**Fix:** Create the instance with `new`.

```javascript
const user = new User("Ava");
```

The corrected version works because `new` creates an object and binds `this` inside the constructor.

### Mistake 2: Trying to access a private field from outside

Private fields use a hard privacy boundary. Code outside the class cannot read or write them.

**Problem:** Accessing a `#private` field from outside the class throws a syntax error because the field is not part of the public object shape.

```javascript
class Account {
  #balance = 100;
}

const account = new Account();
console.log(account.#balance);
```

Output (tested with Node 24.21.0):

```text
Uncaught SyntaxError: Private field '#balance' must be declared in an enclosing class
```

**Fix:** Expose a method if outside code needs read access.

```javascript
class Account {
  #balance = 100;

  getBalance() {
    return this.#balance;
  }
}

const account = new Account();
console.log(account.getBalance());
```

Output (tested with Node 24.21.0):

```text
100
```

The fix works because public methods can safely reveal information without exposing the internal field directly.

### Mistake 3: Losing `this` when passing a method

Class methods rely on `this` pointing to the instance. If you pass a method as a callback, that connection can be lost.

**Problem:** The method is detached from its instance, so `this` becomes `undefined` in strict mode and the code can fail with a TypeError.

```javascript
class Counter {
  count = 0;

  increment() {
    this.count += 1;
  }
}

const counter = new Counter();
const run = counter.increment;
run();
```

**Fix:** Bind the method or wrap it in an arrow function when passing it around.

```javascript
const run = counter.increment.bind(counter);
run();
```

The corrected version works because `bind` permanently links the method to the original instance.

## 7. Best Practices

### Use fields for clear instance shape

Declare the properties your class expects near the top of the class body. That makes the object shape obvious and avoids surprises later.

```javascript
class Session {
  token = null;
  expiresAt = null;
}
```

This is better than adding properties in unrelated methods because readers can see the instance structure immediately.

### Keep validation close to construction

If a class should never represent invalid data, enforce rules in the constructor or dedicated setters.

```javascript
class Rectangle {
  constructor(width, height) {
    if (width <= 0 || height <= 0) {
      throw new RangeError("width and height must be positive");
    }

    this.width = width;
    this.height = height;
  }
}
```

Early validation prevents invalid objects from spreading through your program.

### Use #private for enforced encapsulation, not just convention

Underscore-prefixed properties like `_value` are only a naming convention. If you need true privacy, use `#private` fields.

```javascript
class Cache {
  #store = new Map();

  set(key, value) {
    this.#store.set(key, value);
  }
}
```

This is more reliable because outside code cannot accidentally depend on the internal field.

## 8. Limitations and Edge Cases

- Private fields are enforced by the language, so they cannot be accessed with bracket notation or dynamic property names.
- Each class with `#private` fields creates a separate private namespace. A field named `#value` in one class is unrelated to `#value` in another class.
- Class fields are instance-specific, so storing a function as a field creates one copy per instance rather than one shared prototype method.
- Methods defined in the class body are non-enumerable and live on the prototype, which can affect object inspection and iteration expectations.
- Classes are always in strict mode, so mistakes like using an unbound method often fail more clearly than in older function patterns.
- You cannot declare a private field outside the class body, and you cannot reference it before it is declared in the class definition.

> If a class field seems to be missing, check whether you meant to set a prototype method, an instance field, or a private field. Those three look similar but behave differently.

## 9. Practical Mini Project

Let's build a small task tracker class that uses a constructor, public fields, and a private field for internal state. This example shows how the parts work together in a realistic object.

```javascript
class TaskTracker {
  #tasks = [];
  owner;

  constructor(owner) {
    this.owner = owner;
  }

  addTask(title) {
    this.#tasks.push({
      id: crypto.randomUUID(),
      title,
      done: false
    });
  }

  markDone(id) {
    const task = this.#tasks.find((item) => item.id === id);
    if (task) {
      task.done = true;
    }
  }

  list() {
    return this.#tasks.map((task) => `${task.done ? "[x]" : "[ ]"} ${task.title}`);
  }
}

const tracker = new TaskTracker("Mina");
tracker.addTask("Write documentation");
tracker.addTask("Review examples");
console.log(tracker.list().join("\n"));
```

This mini project uses a public `owner` field, a private `#tasks` array, and methods to modify and read the internal list safely. The private field keeps task data hidden while still allowing the class to manage it internally.

## 10. Key Points

- A class is a blueprint for creating objects with shared behavior.
- The constructor runs when you create an instance with `new`.
- Public fields store per-instance data that outside code can access.
- `#private` fields can only be used inside the class body.
- Class methods live on the prototype and are shared between instances.
- Use classes when the object has meaningful state and behavior.

## 11. Practice Exercise

Build a small `Playlist` class.

- Create a constructor that accepts a playlist name.
- Add a public field for the playlist title.
- Store songs in a private `#songs` field.
- Add methods to `addSong()`, `removeSong()`, and `getSongs()`.
- Ensure outside code cannot modify the private songs array directly.

**Expected output:** creating a playlist, adding songs, and reading them back should work, but direct access to the private song list should fail.

**Hint:** Use `#songs = []` inside the class and return a copy from `getSongs()`.

**Solution:**

```javascript
class Playlist {
  #songs = [];
  title;

  constructor(title) {
    this.title = title;
  }

  addSong(song) {
    this.#songs.push(song);
  }

  removeSong(song) {
    this.#songs = this.#songs.filter((item) => item !== song);
  }

  getSongs() {
    return [...this.#songs];
  }
}

const playlist = new Playlist("Road Trip");
playlist.addSong("Sunrise Drive");
playlist.addSong("Night Lights");
console.log(playlist.getSongs());
```

Output (tested with Node 24.21.0):

```text
[ "Sunrise Drive", "Night Lights" ]
```

This solution keeps the song list private while still giving outside code a safe way to use it.

## 12. Final Summary

JavaScript classes provide a clear way to group initialization, data, and behavior into one object definition. The constructor handles setup, public fields define instance data, and methods describe what the object can do.

`#private` fields are especially valuable when a class owns internal state that should not be touched from the outside. They give you real encapsulation, not just a naming convention. That makes classes more robust when you want to protect invariants such as balances, counters, caches, or task lists.

If you want to go further, the next useful topics are inheritance with `extends` and `super`, static methods, and the difference between class methods and arrow-function fields.

## Browser Support

| Feature | Status | Chrome | Edge | Firefox | Safari | Safari iOS |
| --- | --- | --- | --- | --- | --- | --- |
| Classes | Widely available since Sep 2018 | 42+ | 13+ | 45+ | 9+ | 9+ |

Data: web-features 3.43.0 (MDN Baseline).
