Skip to content
JavaScript js prototypes 4 min read

Constructor Functions

Before the class keyword arrived in ES2015, JavaScript built reusable object “types” with constructor functions — ordinary functions invoked with the new keyword. They are still everywhere: in legacy code, in libraries, and under the hood of every class you write. Understanding them demystifies what new actually does and shows you the real machinery that classes only dress up.

What a constructor function is

A constructor function is just a regular function intended to be called with new. By convention its name is capitalized (User, Point) to signal that intent. Inside the function, this refers to the brand-new object being built, and you attach the per-instance data to it.

function User(name, email) {
  this.name = name;
  this.email = email;
  this.active = true;
}

const alice = new User("Alice", "[email protected]");
const bob = new User("Bob", "[email protected]");

console.log(alice.name);
console.log(bob.email);
console.log(alice.active);

Output:

Alice
[email protected]
true

Each call to new User(...) produces a fresh, independent object. There is no explicit returnnew takes care of returning the constructed object for you.

What new actually does

The new keyword is the real workhorse. When you write new User("Alice", ...), the engine performs four steps:

  1. Creates a brand-new empty object.
  2. Links that object’s [[Prototype]] to User.prototype.
  3. Binds this to the new object and runs the function body.
  4. Returns the new object automatically — unless the function explicitly returns its own (non-primitive) object.
function User(name) {
  // 1 & 2 happen before this body runs:
  // this = a new object whose prototype is User.prototype
  this.name = name; // 3: populate `this`
  // 4: `return this` is implicit
}

const u = new User("Carol");
console.log(u);
console.log(Object.getPrototypeOf(u) === User.prototype);

Output:

User { name: 'Carol' }
true

Forgetting new is a classic bug. User("Dave") (no new) runs the function with this undefined in strict mode, throwing a TypeError; in sloppy mode this leaks to the global object and silently corrupts global state. Always call constructors with new.

Attaching methods to the prototype

If you put methods inside the constructor body, every instance gets its own copy of that function — wasteful when you create thousands of objects. The right place for shared methods is the constructor’s prototype object. Because every instance links to User.prototype, they all share a single function reference via the prototype chain.

function User(name, email) {
  this.name = name;
  this.email = email;
}

// Shared by every instance — defined once.
User.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};

User.prototype.describe = function () {
  return `${this.name} <${this.email}>`;
};

const alice = new User("Alice", "[email protected]");
const bob = new User("Bob", "[email protected]");

console.log(alice.greet());
console.log(bob.describe());
console.log(alice.greet === bob.greet); // same function reference

Output:

Hi, I'm Alice
Bob <[email protected]>
true

The this inside greet refers to whichever instance the method was called on, even though the function itself lives on the prototype. Keep per-instance data on this in the constructor; keep behavior on the prototype.

Where you define itLives onMemory costUse for
this.x = ... in constructorThe instanceOne copy per instancePer-instance data
Fn.prototype.method = ...The prototypeOne copy total, sharedShared behavior

The instanceof check

instanceof tells you whether an object was built from a particular constructor. It works by walking the object’s prototype chain and checking whether the constructor’s .prototype appears anywhere along it — so it respects inheritance, not just the direct constructor.

function User(name) {
  this.name = name;
}

const alice = new User("Alice");

console.log(alice instanceof User);
console.log(alice instanceof Object); // Object.prototype is on the chain
console.log(alice instanceof Array);
console.log([] instanceof Array);

Output:

true
true
false
true

Because instanceof inspects the prototype chain rather than a stored tag, reassigning Fn.prototype after objects exist can make older instances report false. For most code, though, it is the idiomatic way to ask “what kind of object is this?”

Bridge to classes

The ES2015 class syntax is syntactic sugar over exactly this pattern. A class’s constructor body is the constructor function, and methods declared in the class body land on Class.prototype automatically. The two snippets below are functionally equivalent.

// Constructor-function style
function User(name) {
  this.name = name;
}
User.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};

// class style — same prototypes underneath
class UserClass {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return `Hi, I'm ${this.name}`;
  }
}

const a = new User("Eve");
const b = new UserClass("Eve");
console.log(a.greet());
console.log(b.greet());
console.log(typeof UserClass); // a class is still a function

Output:

Hi, I'm Eve
Hi, I'm Eve
function

Classes add real conveniences — they enforce new, support extends/super cleanly, and keep methods non-enumerable — but the underlying model is the prototype mechanism you just saw. Reach for class in new code; understand constructor functions so the magic stops being magic.

Best Practices

  • Capitalize constructor names (User, not user) so callers know to use new.
  • Always invoke constructors with new; in strict mode a missing new throws, which is a feature, not a bug.
  • Put per-instance state on this and shared methods on Fn.prototype to avoid duplicating functions across instances.
  • Use instanceof to test type membership, but remember it walks the prototype chain and can be fooled by prototype reassignment.
  • Prefer regular function declarations for constructors — arrow functions have no this binding and cannot be called with new.
  • For new projects, prefer the class syntax; it compiles down to this exact pattern but is clearer and enforces correct usage.
Last updated June 1, 2026
Was this helpful?