Detailed explanation
An ES6 class and a constructor function both create objects using JavaScript’s prototype-based inheritance model. A constructor function such as function Animal(name) { this.name = name; } initialises an instance when called with new; shared methods are usually assigned to Animal.prototype. A class groups the constructor and methods inside one declaration. In both approaches, an instance’s prototype normally points to Animal.prototype, so methods are shared rather than copied onto every instance. Classes are therefore often called syntactic sugar over prototypes, but they also introduce important semantic rules.
The first difference concerns declaration timing and invocation. A function declaration can be called or constructed before its declaration appears in the scope. A class declaration has a temporal dead zone: accessing it before initialisation throws a ReferenceError. Saying classes are “not hoisted” is convenient shorthand, but the temporal dead zone is the more precise explanation. Classes must be invoked with new; calling Animal() directly throws a TypeError. An ordinary constructor function can be called without new unless its implementation prevents that, although it may fail or modify the wrong object. All code inside a class body runs in strict mode automatically.
Class methods are defined as non-enumerable properties on the prototype, whereas methods added through ordinary prototype assignment are enumerable by default. Instance fields belong to each instance, and static methods belong to the constructor itself. In the example, #id is a private instance field enforced by the language, not a conventional property named with an underscore. A subclass cannot directly access a parent’s private field. Private fields were introduced after ES6, although they are part of modern class syntax.
Classes also provide extends and super for inheritance. For class Dog extends Animal, JavaScript links both the instance prototypes and the constructors, enabling inherited instance and static behaviour. In a derived constructor, super(...) must run before accessing this. The example’s super.speak() invokes the parent method with the current instance as this. Constructor functions can reproduce prototype inheritance manually, but classes express it more clearly and enforce additional rules. Interviewers expect both the shared prototype foundation and these observable differences.
Key points to remember
- Both approaches use prototypes; classes do not introduce a separate inheritance model.
- Class declarations have a temporal dead zone, unlike hoisted function declarations.
- Classes require new, and code inside class bodies always uses strict mode.
- Class methods are non-enumerable; ordinary prototype assignment creates enumerable methods.
- Classes provide extends/super and modern #private fields, which were introduced after ES6.
Common mistakes candidates make
- Saying classes replace prototypes or copy every method onto each instance.
- Claiming all constructor functions are callable before declaration; function expressions depend on how their variable is declared and initialised.
- Treating #private fields as ordinary properties, or incorrectly describing them as an original ES6 feature.
Likely follow-up questions
How would you implement inheritance using constructor functions?
Set Dog.prototype = Object.create(Animal.prototype), then restore Dog.prototype.constructor to Dog. Call Animal.call(this, name) inside Dog to initialise instance properties; use Object.setPrototypeOf(Dog, Animal) if you also want constructor-level inheritance.
What happens if a subclass has no constructor?
JavaScript supplies a default derived constructor that forwards arguments to the parent constructor. If you write your own derived constructor, you must call super(...) before accessing this.
Can a subclass access a parent class’s #private field?
Not directly: a private name is only accessible within the class body that declares it. Parent methods can still access that field on a correctly initialised subclass instance, so subclasses can use public or protected-by-convention methods exposed by the parent.
Interview tip: Start with the shared prototype model, then contrast declaration timing, invocation rules, strict mode, and inheritance using the Animal/Dog example.
