JavaScript Object Constructors

An object constructor is a regular JavaScript function that is designed to be called with the new keyword in order to build many similar objects without repeating the same property-assignment code over and over. Instead of writing out an object literal by hand every time you need a “person” or a “car”, you write the blueprint once as a function, and then stamp out as many instances as you like. Understanding constructors is essential because they are the historical foundation that the modern class syntax is built on top of — under the hood, a class is still just a constructor function with a prototype.

Overview / How it works

In JavaScript, functions are not just callable pieces of code — they are also objects, and every ordinary function has a special property called prototype. This property holds an object that will become the prototype of every instance created from that function when it is invoked with new. A constructor function is simply a function meant to be used this way: by convention it is named with a capital letter (Person, not person) so that anyone reading the code immediately knows “this must be called with new“.

When you write this.name = name; inside a constructor, this refers to the brand-new object that new is in the process of creating. Any properties you attach to this become that instance’s own, enumerable, per-object properties. Methods, on the other hand, are usually placed on ConstructorName.prototype rather than directly on this, because the prototype is shared by every instance — this means one single function in memory is reused by all objects, instead of every object carrying its own private copy of every method (which would waste memory if you created thousands of instances).

JavaScript also ships with a built-in generic Object() constructor. Calling new Object() creates a plain empty object, equivalent to {}. You will rarely use new Object() directly in real code — object literals are shorter and just as capable — but every object literal you write is, in a sense, an instance of the built-in Object constructor, which is why every plain object inherits methods like toString() and hasOwnProperty() from Object.prototype.

Syntax

function ConstructorName(param1, param2) {
  this.prop1 = param1;
  this.prop2 = param2;
}

ConstructorName.prototype.methodName = function () {
  // shared behavior using this.prop1, this.prop2
};

const instance = new ConstructorName(value1, value2);
Part Meaning
function ConstructorName(...) A normal function declaration; capitalized name signals it is meant to be used with new.
this.prop = param Attaches an own property to the object being constructed.
ConstructorName.prototype.methodName Adds a method that every instance will share via the prototype chain.
new ConstructorName(...) Creates a new object, links its prototype, runs the function with this bound to that object, and returns it.

Examples

Example 1: A basic constructor function

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const alice = new Person("Alice", 30);
console.log(alice.name, alice.age);
Output:
Alice 30

Calling new Person("Alice", 30) creates a fresh object, runs the Person function with this pointing at that object, and the two assignment lines give it its own name and age properties. The result is stored in alice, a fully independent object.

Example 2: Sharing methods through the prototype

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function () {
  return `Hi, I'm ${this.name} and I'm ${this.age} years old.`;
};

const bob = new Person("Bob", 25);
console.log(bob.greet());
Output:
Hi, I'm Bob and I'm 25 years old.

Here greet is defined once on Person.prototype, not recreated inside the constructor. Every Person instance can call .greet(), and inside that method this automatically refers to whichever instance made the call.

Example 3: Multiple instances and instanceof

function Car(make, model) {
  this.make = make;
  this.model = model;
}

Car.prototype.describe = function () {
  return `${this.make} ${this.model}`;
};

const car1 = new Car("Toyota", "Corolla");
const car2 = new Car("Honda", "Civic");

console.log(car1.describe());
console.log(car2.describe());
console.log(car1 instanceof Car);
console.log(car1.describe === car2.describe);
Output:
Toyota Corolla
Honda Civic
true
true

Each call to new Car(...) produces a distinct object with its own make and model, but both instances point to the exact same describe function object on the shared prototype, which is why the final comparison prints true. The instanceof operator checks whether Car.prototype appears anywhere in car1‘s prototype chain, confirming it was built by this constructor.

Under the hood: what new actually does

When the JavaScript engine evaluates new ConstructorName(args), it performs four steps, in this exact order:

  1. A brand-new, empty plain object is created in memory.
  2. That new object’s internal prototype link is set to point at ConstructorName.prototype, which is why instances can find methods like greet or describe even though those methods were never copied onto the instance itself.
  3. The constructor function body runs with this bound to the new object, and the arguments you passed are supplied as parameters.
  4. If the constructor does not explicitly return an object, the engine automatically returns the new object that this pointed to. If the constructor does return an object explicitly, that returned object is used instead, and the newly created one is discarded.

That last rule is subtle and trips people up, so it is covered directly in the mistakes below.

Common Mistakes

Mistake 1: Forgetting the new keyword

If you call a constructor like a normal function, without new, this is never bound to a fresh object. In strict mode (which all ES modules and classes use automatically), this is undefined inside a plain function call, so trying to set a property on it throws immediately:

"use strict";

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

const p = Person("Eve");
console.log(p);
Output:
TypeError: Cannot set properties of undefined (setting 'name')

The fix is simply to always call the function with new:

"use strict";

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

const p = new Person("Eve");
console.log(p);
Output:
Person { name: 'Eve' }

In non-strict code the same mistake does not throw an error — instead this silently falls back to the global object, and you end up leaking properties onto it, which is arguably worse because the bug goes unnoticed. This is one of the main reasons modern JavaScript favors class syntax: calling a class constructor without new always throws a clear TypeError, no matter what mode you are in.

Mistake 2: Not understanding how a return value inside a constructor is handled

If a constructor explicitly returns an object, that object silently replaces the one new was building, and anything you attached to this is lost:

function Widget(id) {
  this.id = id;
  return { overridden: true };
}

const w = new Widget(42);
console.log(w);
Output:
{ overridden: true }

But if the constructor returns a primitive value (a string, number, boolean, and so on), the return value is ignored completely and the normally-constructed object is returned instead:

function Widget(id) {
  this.id = id;
  return 42;
}

const w = new Widget(7);
console.log(w);
Output:
Widget { id: 7 }

The safest habit is to never return anything from a constructor at all, and let new do its job automatically.

Best Practices

  • Capitalize constructor function names (Person, Car) so every caller knows to use new.
  • Put shared behavior on ConstructorName.prototype, not inside the constructor body, so it isn’t recreated for every instance.
  • Prefer modern class syntax for new code — it compiles down to the same constructor-plus-prototype mechanics, but enforces new and reads more clearly.
  • Avoid returning a value from a constructor unless you deliberately want to override the created instance — it is a rare, advanced pattern (like implementing a singleton or a factory-like wrapper), not a default habit.
  • Use instanceof to check what a value was constructed from, rather than inspecting property shapes manually.
  • Never rely on non-strict mode’s silent global-object fallback when new is omitted — treat that behavior as a bug to fix, not a feature to use.

Practice Exercises

  • Write a Book constructor that takes title and author parameters and stores them as own properties. Add a summary() method on its prototype that returns a string like "Dune by Frank Herbert".
  • Create two instances of your Book constructor and log book1.summary === book2.summary. Explain in your own words why the result is true even though book1 and book2 are different objects.
  • Write a constructor called Counter that stores a count property starting at 0, with prototype methods increment() and getCount(). Then intentionally call Counter() without new inside a "use strict" script and predict what error you will get before checking.

Summary

  • A constructor function is an ordinary function, conventionally capitalized, intended to be invoked with new.
  • Calling new ConstructorName(...) creates a new object, links its prototype to ConstructorName.prototype, runs the function with this bound to that object, and returns it automatically.
  • Shared methods belong on the prototype, not inside the constructor, so every instance reuses the same function in memory instead of duplicating it.
  • Forgetting new is a classic bug: in strict mode it throws a TypeError; in non-strict mode it silently pollutes the global object.
  • If a constructor explicitly returns an object, that object replaces the newly built instance; returning a primitive is ignored.
  • Modern class syntax builds on exactly this constructor-and-prototype model, while adding safety like enforcing new.