Constructor functions and ES6 classes both create objects with shared methods, but have important differences.
Constructor functions:
Traditional pre-ES6 way to create object types. Just regular functions called with new keyword. Manually add methods to prototype. More verbose but shows how JavaScript works. Greater flexibility in some cases.
ES6 classes:
Modern syntax introduced in ES6. Syntactic sugar over constructor functions and prototypes. Cleaner and more familiar syntax for developers from other languages. Built-in support for inheritance with extends and super. Methods are non-enumerable by default. Must be called with new, cannot call as regular function.
Key differences:
Syntax: Classes are cleaner and more readable. Strict mode: Classes automatically run in strict mode. Hoisting: Functions are hoisted, classes are not. Calling: Functions can be called without new, classes cannot. Method enumeration: Class methods are non-enumerable, function prototype methods are enumerable.
Advantages of constructor functions:
More explicit about prototypes. Can be called without new with checks. More flexibility in dynamic scenarios. Better for understanding JavaScript internals.
Advantages of classes:
Cleaner, more readable syntax. Better for developers from classical OOP languages. Built-in inheritance support. Less boilerplate for common patterns. Industry standard in modern JavaScript.
When to use:
Use classes in modern code for better readability. Use constructor functions when working with legacy code. Understand both because classes use prototypes underneath.
Both approaches ultimately create the same prototype-based structure.
Example code
// CONSTRUCTOR FUNCTION
function PersonFunc(name, age) {
this.name = name;
this.age = age;
}
PersonFunc.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
PersonFunc.prototype.getAge = function() {
return this.age;
};
// Static method
PersonFunc.create = function(name, age) {
return new PersonFunc(name, age);
};
const john = new PersonFunc('John', 30);
john.greet(); // 'Hi, I am John'
// ES6 CLASS
class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I am ${this.name}`);
}
getAge() {
return this.age;
}
static create(name, age) {
return new PersonClass(name, age);
}
}
const jane = new PersonClass('Jane', 25);
jane.greet(); // 'Hi, I am Jane'
// DIFFERENCE 1: Enumeration
// Constructor function methods are enumerable
for (let key in john) {
console.log(key); // name, age, greet, getAge
}
// Class methods are non-enumerable
for (let key in jane) {
console.log(key); // name, age (only own properties)
}
// DIFFERENCE 2: Calling without new
// Function can be called without new (mistakes possible)
const wrong = PersonFunc('Bob', 35); // Forgot new
console.log(wrong); // undefined
// console.log(window.name); // 'Bob' (polluted global!)
// Class must use new
// const wrong2 = PersonClass('Alice', 28); // TypeError!
// DIFFERENCE 3: Hoisting
const p1 = new PersonFunc('Test', 20); // Works (hoisted)
// const p2 = new PersonClass('Test', 20); // ReferenceError!
// (classes not hoisted)
// INHERITANCE COMPARISON
// Constructor function inheritance (verbose)
function AnimalFunc(name) {
this.name = name;
}
AnimalFunc.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function DogFunc(name, breed) {
AnimalFunc.call(this, name);
this.breed = breed;
}
DogFunc.prototype = Object.create(AnimalFunc.prototype);
DogFunc.prototype.constructor = DogFunc;
DogFunc.prototype.bark = function() {
console.log(this.name + ' barks');
};
// Class inheritance (clean)
class AnimalClass {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
}
class DogClass extends AnimalClass {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(this.name + ' barks');
}
}
// WHEN TO USE WHAT
// Use classes (modern, preferred)
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log('Hello ' + this.name);
}
}
// Use constructor functions (legacy, or when needed)
function OldStyleUser(name) {
this.name = name;
}
OldStyleUser.prototype.greet = function() {
console.log('Hello ' + this.name);
};
// Both create same structure
console.log(typeof PersonClass); // 'function'
console.log(PersonClass.prototype.greet); // function
console.log(PersonFunc.prototype.greet); // function