Skip to main content

Class vs constructor function

A class is syntactic sugar over prototypes, but not only sugar: it adds several hard behavioural rules a constructor function does not have. Both mechanisms eventually create an object with a prototype, yet a class forbids calls without new, turns on strict mode, hides methods from enumeration and is not hoisted up the scope.

Theory

TL;DR

  • A class cannot be called without new: that is a TypeError. A constructor function can be, and it silently pollutes the global object or throws.
  • A class body always runs in strict mode, even without 'use strict'.
  • Class methods are non-enumerable, so they never show up in for...in or Object.keys(Prototype).
  • Classes are not hoisted: touching a class before its declaration throws a ReferenceError.
  • Class inheritance is extends plus super, for functions it is manual Object.create and call.
  • Private #field members exist only in classes.

Quick example

javascript
function UserFn(name) { this.name = name; } UserFn("Alice"); // works, but writes to the global object or throws in strict mode class UserClass { constructor(name) { this.name = name; } } UserClass("Alice"); // TypeError: Class constructor UserClass cannot be invoked without 'new'

Syntax and readability

Constructor function:

javascript
function User(name) { this.name = name; } User.prototype.greet = function () { console.log(`Hi, ${this.name}`); }; const user = new User("Alice"); user.greet();

Class:

javascript
class User { constructor(name) { this.name = name; } greet() { console.log(`Hi, ${this.name}`); } } const user = new User("Alice"); user.greet();

The class looks cleaner and more declarative, especially once inheritance appears. A constructor function forces you to wire up prototype, Object.create() and similar things by hand.

Calling without new, and strict mode

A constructor function can be called without new by accident, and that leads to errors: this becomes undefined in strict mode or binds to the global object.

javascript
function User(name) { this.name = name; } const u = User("Alice"); // error, or it creates a global window.name property

A class cannot be called without new, that is an immediate exception:

javascript
class User { constructor(name) { this.name = name; } } const u = User("Alice"); // TypeError: Class constructor User cannot be invoked without 'new'

This makes classes safer. On top of that, the whole class body automatically runs in strict mode even if 'use strict' is written nowhere. A constructor function gives no such guarantee: without an explicit 'use strict' it runs in sloppy mode.

Enumerability, class fields and hoisting

Methods defined inside class {} are non-enumerable, so they do not appear in a for...in walk, whereas methods on the prototype of a constructor function are enumerable:

javascript
class A { m() {} } console.log(Object.keys(A.prototype)); // [] function B() {} B.prototype.m = function() {}; console.log(Object.keys(B.prototype)); // ["m"]

That makes classes "cleaner" for iteration and serialisation.

Inside a class body you could not declare methods as ordinary function expressions:

javascript
class A { m = function() {}; // SyntaxError in older engine versions }

You need the class fields syntax or arrow functions for that, and those were standardised later (ES2022 and newer).

Finally, classes are not hoisted. Constructor functions can be called before they are declared:

javascript
const a = new User(); function User() {}

Classes do not work that way:

javascript
const a = new User(); // ReferenceError class User {}

This prevents surprising runtime errors.

Inheritance

With constructor functions you have to write a lot of boilerplate:

javascript
function Animal(name) { this.name = name; } Animal.prototype.speak = function() { console.log(`${this.name} makes a sound`); }; function Dog(name) { Animal.call(this, name); } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog;

And with classes:

javascript
class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound`); } } class Dog extends Animal { speak() { console.log(`${this.name} barks`); } }

Short and clear. A class also gives you super for reaching the parent implementation, while with functions you have to call Animal.prototype.speak.call(this) manually.

Summary table

DifferenceClassConstructor function
Call without newTypeErrorWorks, but leads to bugs
Strict modeAlways onOff by default
HoistingNoYes
Methods enumerableNoYes
Inheritance syntaxSimple (extends, super)Complex (Object.create, call)
Private fieldsYes (#field)No
ReadabilityHighLow

Common mistakes

  • Saying in an interview that "a class is just sugar" and stopping there. The behavioural differences (calls without new, strict mode, hoisting, enumerability) are exactly what is being tested.
  • Confusing the lack of hoisting with the declaration not being hoisted at all: the class binding is hoisted but sits in the temporal dead zone, which is why you get a ReferenceError and not is not defined.
  • Forgetting Dog.prototype.constructor = Dog in manual inheritance and then wondering why instance.constructor points at Animal.
  • Treating _field in a constructor function as private. Real privacy exists only in class fields with #.
  • Passing a class method as a callback and losing this: neither a class nor a constructor function binds the context for you.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.