Q37 of 40 · JavaScript
What are the practical differences between ES6 `class` and prototype-based patterns, and when does it matter?
JavaScriptSeniorjavascriptclassprototypesoopprivate-fieldsinheritance
Short answer
Short answer: `class` is syntactic sugar over prototypes — methods go on the prototype, instances share them. Practical differences: `class` enforces `new`, supports `super`, and `class` bodies are in strict mode. Private fields (`#field`) are a true encapsulation mechanism not achievable with prototype patterns.
Detail
ES6 class compiles to roughly the same prototype chain structure but with several behavioral differences that matter in practice.
What class adds over manual prototypes:
- Mandatory new: Calling a class without
newthrowsTypeError— unlike constructor functions which silently pollute the global object in sloppy mode. - super:
classprovides clean, spec-compliant super-call semantics for derived class constructors. - Strict mode: Class bodies are always in strict mode.
- Private fields:
#fieldsyntax provides true encapsulation (not accessible outside the class body), enforced at the language level — not possible with prototype patterns. - Static blocks:
static { ... }for complex static initialization. - Non-enumerable methods: Methods defined in class bodies are non-enumerable on the prototype — unlike
Foo.prototype.method = function() {}which is enumerable.
When it matters for testing:
- Private fields cannot be accessed in tests — you must test them through public API, or use
--expose-gc+#fieldaccessor workarounds. typeof MyClassis 'function' — it's a function under the hood.- Mocking a class method can be done on the prototype:
MyClass.prototype.method = jest.fn().
// EXAMPLE
// Prototype pattern (pre-ES6 style)
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return `${this.name} speaks`; };
// class — equivalent but cleaner, with extras
class Animal2 {
#name; // private — not accessible outside
constructor(name) { this.#name = name; }
speak() { return `${this.#name} speaks`; }
get name() { return this.#name; } // public accessor
}
// class methods are non-enumerable
console.log(Object.keys(Animal2.prototype)); // []
// vs prototype assignment: Animal.prototype.speak is enumerable
// Mocking a class method in Jest
jest.spyOn(Animal2.prototype, "speak").mockReturnValue("mocked");
// Private field — not accessible in test
const a = new Animal2("Dog");
// console.log(a.#name); // SyntaxError — truly private// WHAT INTERVIEWERS LOOK FOR
Non-enumerable methods, mandatory new, private fields as true encapsulation, and strict mode. Connecting to test patterns — mocking via prototype and the limitation of testing private fields. This shows depth beyond 'class is just syntactic sugar'.
// COMMON PITFALL
Thinking private fields (`#field`) are just a convention like `_field` — they are enforced at the language level. A test that tries to access `instance.#field` will throw a SyntaxError, not just return undefined.