Class basic syntax

Most real programs churn out many objects of the same shape: users, products, orders, DOM widgets. You want one blueprint and a cheap way to stamp out instances from it.

You have already seen one tool for that in Constructor, operator “new”: a plain function called with new. The class construct is the modern, purpose-built version of the same idea. It reads more clearly and layers on features that the hand-rolled approach can’t easily match.

The “class” syntax

Here is the shape of a class declaration:

class MyClass {
  // class methods
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}

Call new MyClass() and you get an object that has all of those methods available.

The constructor() method is special: new runs it automatically, passing along whatever arguments you gave. That is where you set up the initial state of the instance.

A concrete example:

class Member {

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

  greet() {
    alert(this.name);
  }

}

// Usage:
let member = new Member("Maya");
member.greet();

Here is what happens when new Member("Maya") runs:

  1. A fresh empty object is created and bound to this.
  2. The constructor runs with "Maya" and stores it as this.name.
  3. The new object is returned.

After that, member.greet() reaches the method and shows "Maya".

Stamp out your own instances below. Each new Member(...) runs the constructor to store a name, then the shared greet() method reads it back:

interactiveBuild instances from one class
1. new creates
{}
this → empty object
2. constructor runs
name: “Maya”
stored on the instance
Member.prototype
greet()
shared by all members
new Member('Maya') step by step: constructor fills the instance, methods live on the prototype.

What is a class?

A class is not some brand-new kind of value bolted onto the language. Pull back the curtain and it turns out to be built from things you already know. Seeing that machinery makes the trickier behavior later much easier to reason about.

In JavaScript, a class is a function.

class Member {
  constructor(name) { this.name = name; }
  greet() { alert(this.name); }
}

// proof: Member is a function
alert(typeof Member); // function

The class Member {...} declaration does two concrete things:

  1. It creates a function named Member. The body of that function comes from the constructor method (an empty body is assumed if you don’t write one). This function is the value you get when you reference Member.
  2. It stores the other methods, like greet, on Member.prototype.

When you create an object with new Member and call a method on it, the lookup walks up to the prototype, exactly as described in F.prototype. That prototype link is how instances reach the shared methods.

Member
the constructor
prototype ↓
Member.prototype
constructor: Member
greet: function
new Member(“Maya”)
name: “Maya”
[[Prototype]] → Member.prototype
A class declaration produces a constructor function whose .prototype holds the methods; each instance links back to it.

You can verify all of this directly:

class Member {
  constructor(name) { this.name = name; }
  greet() { alert(this.name); }
}

// class is a function
alert(typeof Member); // function

// ...or, more precisely, the constructor method
alert(Member === Member.prototype.constructor); // true

// The methods are in Member.prototype, e.g:
alert(Member.prototype.greet); // the code of the greet method

// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(Member.prototype)); // constructor, greet

Not just syntactic sugar

You will sometimes hear that class is “syntactic sugar” — syntax that reads more nicely but adds nothing genuinely new — because you can build the equivalent by hand with a constructor function and prototype methods:

// rewriting class Member in pure functions

// 1. Create constructor function
function Member(name) {
  this.name = name;
}
// a function prototype has "constructor" property by default,
// so we don't need to create it

// 2. Add the method to prototype
Member.prototype.greet = function() {
  alert(this.name);
};

// Usage:
let member = new Member("Maya");
member.greet();

The end result is close enough that the “sugar” label has some truth to it. But the two are not identical, and the differences matter.

  1. A class constructor is tagged. Internally it carries [[IsClassConstructor]]: true. The engine checks that flag in several places. The most visible consequence: a class must be called with new, and calling it plainly throws.

    class Member {
      constructor() {}
    }
    
    alert(typeof Member); // function
    Member(); // Error: Class constructor Member cannot be invoked without 'new'

    The string form is different too. In most engines, converting a class to a string starts with the word class:

    class Member {
      constructor() {}
    }
    
    alert(Member); // class Member { ... }
  2. Class methods are non-enumerable. A class marks every method on prototype with enumerable: false. That is the behavior you want, because iterating an object with for..in then skips the class methods and visits only its own data properties.

  3. Class bodies always run in strict mode. Everything inside the class construct is automatically strict — assignments to undeclared variables throw, this in a plain function call is undefined, and so on.

Beyond these, class unlocks more features covered in later chapters (private fields, static, super, and inheritance).

Class Expression

Classes are values, just like functions. You can put a class inside an expression, assign it, pass it as an argument, or return it from a function.

A class expression looks like this:

let Member = class {
  greet() {
    alert("Welcome");
  }
};

Function expressions can carry an internal name (a Named Function Expression), and class expressions can too. That name is visible only inside the class body:

// "Named Class Expression"
// (no such term in the spec, but that's similar to Named Function Expression)
let Member = class Blueprint {
  greet() {
    alert(Blueprint); // Blueprint name is visible only inside the class
  }
};

new Member().greet(); // works, shows Blueprint definition

alert(Blueprint); // error, Blueprint name isn't visible outside of the class

Because a class is a value, you can also build one on the fly and return it from a factory function:

function makeClass(phrase) {
  // declare a class and return it
  return class {
    greet() {
      alert(phrase);
    }
  };
}

// Create a new class
let Member = makeClass("Welcome");

new Member().greet(); // Welcome

Notice the returned class closes over phrase, so each generated class remembers its own message.

Getters/setters

Anything an object literal can hold, a class body can hold too — including getters and setters and computed property names.

Here user.name is backed by a getter/setter pair that validates the value:

class Member {

  constructor(name) {
    // invokes the setter
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short.");
      return;
    }
    this._name = value;
  }

}

let member = new Member("Maya");
alert(member.name); // Maya

member = new Member(""); // Name is too short.

The real data lives in this._name, while name is an accessor sitting on Member.prototype. Assigning this.name = name in the constructor already runs the setter, so the validation fires even during construction.

Type into the field below and watch the setter accept or reject each value. Anything shorter than four characters is refused, and this._name keeps its previous accepted value:

interactiveA setter that guards its data

Computed names […]

You can compute a method name at definition time by wrapping an expression in square brackets, the same way you would for a computed key in an object literal:

class Member {

  ['gr' + 'eet']() {
    alert("Welcome");
  }

}

new Member().greet();

The expression 'gr' + 'eet' evaluates to "greet", so that becomes the method name.

Class fields

Everything so far lived on the prototype. Class fields let you declare properties that land directly on each instance.

Add a name field to Member:

class Member {
  name = "Maya";

  greet() {
    alert(`Hello, ${this.name}!`);
  }
}

new Member().greet(); // Hello, Maya!

The syntax is <property name> = <value> written straight in the class body — no constructor required.

The key distinction: a field is set on each individual instance, not on Member.prototype.

class Member {
  name = "Maya";
}

let member = new Member();
alert(member.name); // Maya
alert(Member.prototype.name); // undefined
Member.prototype
greet: function
name: undefined
member1
name: “Maya”
member2
name: “Maya”
Methods are shared on the prototype; class fields are copied onto every instance.

Field values are not restricted to constants. Any expression works, including function calls, and it is evaluated per instance when the object is created:

class Member {
  name = prompt("Name, please?", "Maya");
}

let member = new Member();
alert(member.name); // Maya

Making bound methods with class fields

Recall from Function binding that this in JavaScript is decided by how a function is called, not where it was defined. Pull a method off its object and call it elsewhere, and this no longer points at the original object.

This code shows the trap:

class Toggle {
  constructor(label) {
    this.label = label;
  }

  press() {
    alert(this.label);
  }
}

let toggle = new Toggle("power");

setTimeout(toggle.press, 1000); // undefined

setTimeout stores the press function and later calls it on its own, with no object in front of it, so this is not the toggle. That is the “losing this” problem.

Function binding gave you two fixes:

  1. Wrap the call so the object stays in front: setTimeout(() => toggle.press(), 1000).
  2. Bind the method to the object, usually in the constructor.

Class fields offer a third, cleaner option — define the method as an arrow function stored in a field:

class Toggle {
  constructor(label) {
    this.label = label;
  }
  press = () => {
    alert(this.label);
  }
}

let toggle = new Toggle("power");

setTimeout(toggle.press, 1000); // power

press = () => {...} creates a separate function on each Toggle instance. An arrow function has no this of its own, so it captures this from the surrounding scope — the instance being constructed. Detach toggle.press and pass it anywhere; this stays correct.

The demo below builds one toggle of each kind and hands its press method to setTimeout — detached, with no object in front of it. The prototype method loses this and reports undefined; the arrow-function field keeps it:

interactiveLosing this vs. an arrow-function field

That per-instance binding is handiest in the browser, where you hand methods to event listeners and timers all the time.

Summary

A full-featured class body can contain fields, a constructor, methods, accessors, and computed-name members:

class MyClass {
  prop = value; // property

  constructor(...) { // constructor
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter method
  set something(...) {} // setter method

  [Symbol.iterator]() {} // method with computed name (symbol here)
  // ...
}

Under the hood, MyClass is a function — the one built from your constructor. Methods, getters, and setters go on MyClass.prototype, while class fields are written onto each instance. On top of that, a class enforces strict mode, hides its prototype methods from for..in, and can’t be called without new.

The next chapters build on this foundation: inheritance with extends and super, static members, and private fields.