DEV Community

Cover image for Constructor Function in JavaScript
capscode
capscode

Posted on • Originally published at capscode.in

Constructor Function in JavaScript

What is a Constructor Function?

Both regular functions and constructor functions are just JavaScript functions. The difference lies in how they are used and whether they create objects when called with new.

A constructor function in JavaScript is a special type of function used to create and initialize objects. It acts as a blueprint for creating multiple instances of similar objects.

Key Characteristics of a Constructor Function:

  1. A regular function that is called with the new keyword.
  2. Used to initialize object properties.
  3. By convention, named with a capitalized first letter (e.g., Person, Car).

Example:

function Person(name, age) {
  this.name = name; // Assign instance properties
  this.age = age; // Assign instance properties
}

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

console.log(person1.name); // "Alice"
console.log(person2.age);  // 30
console.log(person1 instanceof Person); // true
Enter fullscreen mode Exit fullscreen mode

Adding methods to a constructor function

Inefficient Way (Defining Methods Inside Constructor)

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function () { // BAD: Creates a new function for every instance
    return `Hello, my name is ${this.name}`;
  };
}

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

console.log(person1.greet === person2.greet); // false (different function instances)
Enter fullscreen mode Exit fullscreen mode

If you define a method inside the constructor, each instance will get its own copy, which is inefficient:

🚨 Problem:
Every time a new Person object is created, a new greet function is created in memory.

Efficient Way (Using prototype for shared methods)

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

// Adding a shared method to the prototype
Person.prototype.greet = function () {
  return `Hello, my name is ${this.name}`;
};

const person1 = new Person("Alice", 25);
const person2 = new Person("Bob", 30);

console.log(person1.greet()); // "Hello, my name is Alice"
console.log(person1.greet === person2.greet); // true (both share the same function)
Enter fullscreen mode Exit fullscreen mode

Let's see why this approach is efficient.

Read more about __proto__ and prototype here

Why using .prototype is Better?

  1. Methods defined on Person.prototype are shared by all instances, reducing memory usage.
  2. JavaScript looks up methods through the prototype chain when calling them.

📌 Prototype Chain:

person1 --> Person.prototype --> Object.prototype --> null
Enter fullscreen mode Exit fullscreen mode

NOTE: JavaScript uses a prototype-based inheritance system (not class-based like some other languages, although ES6 introduced class syntax which is syntactic sugar over prototypes).

Read about prototype chain here


The constructor Property

Every function in JavaScript automatically has a prototype object containing a constructor property along with [[Prototype]] (hidden).

console.log(Person.prototype.constructor === Person); // true
console.log(person1.constructor === Person); // true
Enter fullscreen mode Exit fullscreen mode

Ensures instances can always identify their constructor function.


Checking an Object’s Constructor

Use instanceof to check if an object was created by a constructor:

console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true
Enter fullscreen mode Exit fullscreen mode

instanceof checks if an object inherits from a constructor’s prototype.


Alternative: Using Object.create() Instead of new

If you don’t want to use new, you can manually set the prototype using Object.create().

const personPrototype = {
  greet: function () {
    return `Hello, my name is ${this.name}`;
  }
};

const person1 = Object.create(personPrototype);
person1.name = "Alice";
console.log(person1.greet()); // "Hello, my name is Alice"
Enter fullscreen mode Exit fullscreen mode

Allows manual prototype setting without constructor functions.

Read how Object.create() works step by step here


Enforcing new Usage with new.target

Inside a constructor function, new.target refers to the function only if called with new.

function Person(name) {
  if (!new.target) {
    throw new Error("Must use 'new' with Person()");
  }
  this.name = name;
}

const p1 = new Person("Alice"); // Works
const p2 = Person("Bob"); // Error: Must use 'new' with Person()
Enter fullscreen mode Exit fullscreen mode

NOTE: Using new.target prevents accidental function calls without new.


Constructor Function vs. Class Syntax (ES6)

ES6 introduced class syntax, which is syntactic sugar over constructor functions.
Read more about classes in JavaScript here

Example:

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

  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

const person1 = new Person("Alice", 25);
console.log(person1.greet()); // "Hello, my name is Alice"
console.log(person1 instanceof Person); // true
Enter fullscreen mode Exit fullscreen mode

Key Differences:

  1. class syntax is more readable.
  2. Methods in class are automatically added to the prototype (no need to explicitly use Person.prototype).

What Happens When You Call new Circle()?

Example:

function Circle() {}
const circle = new Circle();
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Breakdown:

  1. A new empty object is created internally:
   const circle = {}; // (internally)
Enter fullscreen mode Exit fullscreen mode
  1. The prototype is set:
   circle.__proto__ = Circle.prototype;
Enter fullscreen mode Exit fullscreen mode

This sets up the prototype chain, so circle can inherit methods defined on Circle.prototype

  1. Any properties or methods added using this inside the function are assigned directly to the new object and any properties or methods defined on Circle.prototype will be available to circle (via prototype chaining).

  2. The new object is returned.

Checking the Prototype Relationship:

console.log(circle.__proto__ === Circle.prototype); // true
Enter fullscreen mode Exit fullscreen mode

Prototype Chain:

circle --> Circle.prototype --> Object.prototype --> null
Enter fullscreen mode Exit fullscreen mode

Adding Methods to Circle.prototype

function Circle(radius) {
  this.radius = radius;
}

Circle.prototype.getArea = function () {
  return Math.PI * this.radius * this.radius;
};

const smallCircle = new Circle(2);
const bigCircle = new Circle(5);

console.log(smallCircle.getArea()); // 12.57
console.log(bigCircle.getArea());   // 78.54
console.log(smallCircle.__proto__ === Circle.prototype); // true
Enter fullscreen mode Exit fullscreen mode

Read more about __proto__ and prototype here


Method Overriding in Prototypes

Instances can override prototype methods if needed:

smallCircle.getArea = function () {
  return "Overridden method!";
};

console.log(smallCircle.getArea()); // "Overridden method!"
console.log(bigCircle.getArea());   // 78.54 (still uses prototype)
Enter fullscreen mode Exit fullscreen mode

Inspecting Circle.prototype

function Circle() {}
console.log(Circle.prototype); // {}
console.log(Circle.prototype.constructor === Circle); // true
console.log(Object.getPrototypeOf(smallCircle) === Circle.prototype); // true
console.log(smallCircle instanceof Circle); // true
Enter fullscreen mode Exit fullscreen mode
  1. JavaScript automatically creates an object called Circle.prototype.

  2. This Circle.prototype object is used to define shared methods or properties that all instances of Person will inherit via their [[Prototype]] (aka __proto__).

  3. Circle.prototype.constructor points back to Circle.


What’s Inside an Instance circle?

Instance Properties (Directly on circle)

function Circle(radius) {
  this.radius = radius; // Own property
  this.describe = function () { // Own method
    return `Radius: ${this.radius}`;
  };
}
Enter fullscreen mode Exit fullscreen mode

Prototype Methods (Inherited)

Circle.prototype.getArea = function () {
  return Math.PI * this.radius * this.radius;
};

const circle = new Circle(5);
console.log(circle.hasOwnProperty("getArea")); // false
console.log("getArea" in circle); // true (inherited)
Enter fullscreen mode Exit fullscreen mode

Checking Properties:

  1. Own properties:
console.log(Object.keys(circle)); // ["radius", "describe"]
Enter fullscreen mode Exit fullscreen mode
  1. All properties (including inherited):
for (let key in circle) {
  console.log(key); // "radius", "describe", "getArea"
}
Enter fullscreen mode Exit fullscreen mode

NOTE: (Important)

  1. for...in loop
    Iterates over all enumerable properties, including both own properties and inherited properties (from the prototype chain).

  2. Object.keys(circle)
    Returns an array of the object's own enumerable properties (excluding inherited properties).

Internal Structure of circle:

{
  radius: 5,
  describe: function() { ... },
  __proto__: {
    getArea: function() { ... },
    constructor: Circle
  }
}
Enter fullscreen mode Exit fullscreen mode

Difference Between Constructor Function and Regular Function

Feature Regular Function Constructor Function
Usage Called normally Called with new
Purpose Executes code Creates objects
Naming Convention camelCase (doSomething) PascalCase (Person)
Prototype Linkage No Yes
Return Behavior Returns explicitly Returns this (new object)

Q: Is Capitalization Mandatory for Constructor Functions?

No, but it’s a best practice to use PascalCase (e.g., Person) to indicate that the function should be called with new.


Summary

In JavaScript, constructor functions serve as blueprints for creating multiple objects with similar properties and methods. When invoked with the new keyword, these functions automatically create a new object, set its prototype to the constructor's prototype property, bind this to the new object, and return it. By convention, constructor functions are named with PascalCase (like Person or Car) to distinguish them from regular functions. For efficient memory usage, methods should be added to the constructor's prototype rather than defined inside the function itself, allowing all instances to share the same method references. The prototype chain enables inheritance, where objects can access properties and methods from their constructor's prototype. Modern JavaScript offers class syntax as a cleaner alternative, but under the hood, classes still use this constructor/prototype pattern. Understanding constructor functions is fundamental to grasping JavaScript's object-oriented capabilities and prototypal inheritance system.

Top comments (0)