ES6 Classes: Private Fields, Static & super Internals
Class sugar hai, # private asli hai. Aur execution order mat bhoolna — child field parent constructor ko mita deta hai!
Classes are syntactic sugar over constructor functions and prototypes. Under the hood, class Foo {} is roughly equivalent to function Foo() {} with methods on Foo.prototype.
new — throws TypeError. And typeof class {} === 'function' — a class IS a function at runtime.// Class syntax
class User {
constructor(name) {
this.name = name;
}
greet() {
return 'Hi, ' + this.name;
}
}
// Equivalent ES5 Constructor
function UserEs5(name) {
this.name = name;
}
UserEs5.prototype.greet = function() {
return 'Hi, ' + this.name;
};
const sai = new User('Sai');
console.log(typeof User); // 'function'
console.log(sai.greet()); // 'Hi, Sai'ES2022 private fields start with #. They are truly private — not accessible outside the class body, not even via bracket notation or DevTools tricks.
Key rules: # fields are added to the instance, not the prototype. Accessing a private field on an object that doesn't have it throws TypeError (brand check). Private methods also exist: #privateMethod() {}. Static private: static #helper() {} — private to the class, not instances. Unlike closure-based privacy, # fields survive destructuring and prototype changes.
class BankAccount {
#balance = 0; // Private field
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance += amount;
}
get balance() {
return this.#balance;
}
#validate(amount) { // Private method
return amount > 0;
}
}
const acc = new BankAccount(100);
acc.deposit(50);
console.log(acc.balance); // 150
// console.log(acc.#balance); // SyntaxError!
// acc.#validate(10); // SyntaxError!
// Brand check: Objects without #balance throw TypeError
const fake = Object.create(acc);
// fake.#balance; // TypeError!
#field, V8 checks if the object's hidden class has the brand. If not, it throws TypeError. This is a fast O(1) check, much faster than closure-based privacy.Static fields and methods belong to the class itself, not instances. Accessed via ClassName.method().
Static fields are defined on the constructor function object. Static initialization blocks: static { ... } — runs once when the class is defined. Can access private static fields. Static #private: Only accessible from static methods or static blocks. this inside static methods refers to the class itself (useful for inheritance).
class AppConfig {
static version = '1.0.0'; // Static field
static #apiUrl = 'https://api.dev'; // Private static
// Static initialization block
static {
console.log('AppConfig initialized');
if (!this.#apiUrl) throw new Error('API URL missing');
}
static getApiUrl() {
return this.#apiUrl; // Access private static
}
}
console.log(AppConfig.version); // '1.0.0'
console.log(AppConfig.getApiUrl()); // 'https://api.dev'
// const cfg = new AppConfig();
// cfg.version; // undefined (static belongs to class)
super in a method resolves via the method's [[HomeObject]], which is set when the method is defined in a class or object literal.
super.method() is NOT Parent.prototype.method.call(this). It dynamically resolves based on [[HomeObject]]'s prototype. super() in constructor: Must be called before accessing this. Calls the parent constructor. If you extract a method from a class and call it standalone, super still works because [[HomeObject]] is bound at definition time.
class Animal {
constructor(name) { this.name = name; }
speak() { return this.name + ' makes a noise'; }
}
class Dog extends Animal {
constructor(name) {
super(name); // Must call before 'this'
this.type = 'dog';
}
speak() {
return super.speak() + ', specifically a bark';
}
}
const d = new Dog('Rex');
console.log(d.speak());
// 'Rex makes a noise, specifically a bark'
super.speak() is called, V8 looks up [[HomeObject]]'s prototype (which is Animal.prototype) and calls speak there. This is why super still works even if you extract the method.When creating a subclass instance, the execution order is critical and often misunderstood:
1. Parent class fields → name = 'Parent Name'
2. Parent constructor body → this.name = 'Parent Constructor Name'
3. Child class fields → name = 'Child Name' (OVERWRITES!)
4. Child constructor body → runs after
This means child fields OVERWRITE values set by the parent constructor! This is the #1 source of bugs with class fields and inheritance.
class Parent {
name = 'Parent Name'; // Step 1
constructor() {
console.log('Parent constructor'); // Step 2
this.name = 'Parent Constructor Name';
}
}
class Child extends Parent {
name = 'Child Name'; // Step 3: OVERWRITES!
constructor() {
super();
console.log('Child constructor'); // Step 4
}
}
const c = new Child();
// Logs: "Parent constructor", "Child constructor"
console.log(c.name); // 'Child Name' — Parent's work overwritten!
• Avoid initializing the same field in both parent class fields AND parent constructor
• Avoid defining the same field name in both parent and child class fields
• Choose ONE place for initialization — either class field OR constructor, not both
Lo kar liya — Key Points:
- ✅ Classes are syntactic sugar over prototypes;
typeof classis 'function'; they are always strict and requirenew - ✅
#private fields are true engine-level privacy, enforced by V8 brand checks, not closures - ✅ Static fields/methods belong to the class, not instances; static blocks run once at definition time
- ✅
superin methods resolves via [[HomeObject]] (bound at definition);super()must be called beforethisin subclass constructors - ✅ Class field execution order: Parent fields → Parent constructor → Child fields → Child constructor
- ✅ Child class fields overwrite values set by the parent constructor — a common inheritance trap
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login