Class Advanced Features
MedBeyond basic class syntax, JavaScript classes support private fields (#), static members, static initialization blocks, computed property names, and patterns like mixins and method chaining. Private fields provide true encapsulation enforced by the engine.
Interactive Visualization
Tagged Templates
A tagged template literal is called
highlight`Hello ${name}!`
Key Points
- Private fields (#) are truly private — accessing them outside the class throws a SyntaxError
- Static methods and properties belong to the class itself, not instances
- Static initialization blocks (static { }) run once when the class is defined
- Public class fields are declared at the top of the class body without let/const
- Mixins compose behavior from multiple sources since JS only supports single inheritance
- Method chaining works by returning this from each method
Code Examples
Private Fields and Methods
class BankAccount { #balance = 0 #owner constructor(owner, initialBalance) { this.#owner = owner this.#balance = initialBalance } #validate(amount) { if (amount <= 0) throw new Error('Amount must be positive') if (amount > this.#balance) throw new Error('Insufficient funds') } withdraw(amount) { this.#validate(amount) this.#balance -= amount return this.#balance } get balance() { return this.#balance } } const account = new BankAccount('Alice', 1000) console.log(account.balance) // 1000 // account.#balance // SyntaxError!
The # prefix creates truly private fields and methods enforced by the engine, not by convention.
Static Members and Init Blocks
class Config { static #instance = null static DEFAULT_TIMEOUT = 5000 #settings constructor(settings) { this.#settings = settings } static { Config.#instance = new Config({ timeout: Config.DEFAULT_TIMEOUT, }) } static getInstance() { return Config.#instance } get(key) { return this.#settings[key] } } const config = Config.getInstance() console.log(config.get('timeout')) // 5000
Static init blocks run when the class is first evaluated — useful for singletons and configuration.
Mixin Pattern
const Serializable = (Base) => class extends Base { toJSON() { const obj = {} for (const key of Object.keys(this)) obj[key] = this[key] return JSON.stringify(obj) } } const Validatable = (Base) => class extends Base { validate() { for (const [key, value] of Object.entries(this)) { if (value == null) throw new Error(key + ' is required') } return true } } class BaseUser { constructor(name, email) { this.name = name; this.email = email } } class User extends Serializable(Validatable(BaseUser)) {} const user = new User('Alice', 'alice@example.com') user.validate() // true console.log(user.toJSON())
Mixins use higher-order class expressions to compose behavior since JS only supports single inheritance.
Method Chaining
class QueryBuilder { #table = '' #conditions = [] #limit = null from(table) { this.#table = table; return this } where(cond) { this.#conditions.push(cond); return this } take(n) { this.#limit = n; return this } build() { let q = 'SELECT * FROM ' + this.#table if (this.#conditions.length) q += ' WHERE ' + this.#conditions.join(' AND ') if (this.#limit) q += ' LIMIT ' + this.#limit return q } } const query = new QueryBuilder() .from('users') .where('age > 18') .where('active = true') .take(10) .build()
Each method returns this, enabling fluent chaining. The builder pattern constructs complex objects step-by-step.
Common Mistakes
- Trying to access private fields with bracket notation — obj["#field"] does not work
- Calling static methods on instances instead of the class
- Arrow function class fields create a new function per instance — performance concern
- Using mixins without understanding the prototype chain order
Interview Tips
- Contrast private fields (#) with underscore convention — # is engine-enforced
- Static init blocks allow complex setup with access to private static fields
- Show how mixins solve the lack of multiple inheritance
- Method chaining key insight: returning this from each method