Docs

README

8.1 Class Basics

Overview

ES6 classes provide a cleaner, more intuitive syntax for creating objects and implementing inheritance. While classes are syntactic sugar over JavaScript's prototype-based inheritance, they offer a more familiar structure for developers coming from other languages.


Table of Contents

  1. •Class Syntax
  2. •Constructor Method
  3. •Instance Methods
  4. •Getter and Setter Methods
  5. •Computed Method Names
  6. •Class Expressions
  7. •Classes vs Constructor Functions

Class Syntax

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                     Class Declaration                        │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                             │
│   class ClassName {                                         │
│       constructor(params) {      ◄── Called with `new`      │
│           this.property = value;                            │
│       }                                                     │
│                                                             │
│       method() {                 ◄── Instance method         │
│           // ...                                            │
│       }                                                     │
│                                                             │
│       get accessor() {           ◄── Getter                  │
│           return this._value;                               │
│       }                                                     │
│                                                             │
│       set accessor(val) {        ◄── Setter                  │
│           this._value = val;                                │
│       }                                                     │
│   }                                                         │
│                                                             │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Basic Class Declaration

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, I'm ${this.name}`;
  }

  haveBirthday() {
    this.age++;
    return `Happy birthday! Now ${this.age}`;
  }
}

const person = new Person('Alice', 30);
console.log(person.greet()); // "Hello, I'm Alice"

Key Characteristics

FeatureDescription
HoistingClass declarations are NOT hoisted
Strict modeClass body always runs in strict mode
new requiredCannot call class without new
EnumerableMethods are non-enumerable
typeoftypeof ClassName returns "function"

Constructor Method

The constructor is a special method called when creating a new instance.

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    Constructor Flow                          │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                                                             │
│   new Person("Alice", 30)                                   │
│         │                                                   │
│         ā–¼                                                   │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”                           │
│   │ 1. Create empty object      │                           │
│   │ 2. Set [[Prototype]]        │                           │
│   │ 3. Bind `this` to object    │                           │
│   │ 4. Run constructor body     │                           │
│   │ 5. Return object            │                           │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                           │
│         │                                                   │
│         ā–¼                                                   │
│   { name: "Alice", age: 30 }                                │
│                                                             │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Constructor Rules

class Example {
  // Constructor is optional
  // If omitted, an empty constructor is used: constructor() {}

  constructor(value) {
    // Initialize instance properties
    this.value = value;

    // Can call methods
    this.initialize();

    // Can return an object (overrides default return)
    // return { custom: true }; // Unusual but possible
  }

  initialize() {
    this.initialized = true;
  }
}

Default Parameter Values

class Config {
  constructor(options = {}) {
    this.host = options.host || 'localhost';
    this.port = options.port || 3000;
    this.debug = options.debug ?? false;
  }
}

const config1 = new Config();
const config2 = new Config({ port: 8080, debug: true });

Instance Methods

Methods defined in the class body are added to the prototype.

class Calculator {
  constructor(initialValue = 0) {
    this.value = initialValue;
  }

  add(n) {
    this.value += n;
    return this; // Enable chaining
  }

  subtract(n) {
    this.value -= n;
    return this;
  }

  multiply(n) {
    this.value *= n;
    return this;
  }

  divide(n) {
    if (n === 0) throw new Error('Division by zero');
    this.value /= n;
    return this;
  }

  reset() {
    this.value = 0;
    return this;
  }

  getResult() {
    return this.value;
  }
}

const calc = new Calculator(10);
calc.add(5).multiply(2).subtract(10);
console.log(calc.getResult()); // 20

Method Sharing

const calc1 = new Calculator();
const calc2 = new Calculator();

// Methods are shared on prototype
console.log(calc1.add === calc2.add); // true

// Properties are per-instance
calc1.value = 100;
console.log(calc2.value); // 0 (unchanged)

Getter and Setter Methods

Accessors allow controlled access to properties.

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }

  // Getter
  get celsius() {
    return this._celsius;
  }

  // Setter
  set celsius(value) {
    if (value < -273.15) {
      throw new Error('Temperature below absolute zero');
    }
    this._celsius = value;
  }

  // Computed property getter
  get fahrenheit() {
    return (this._celsius * 9) / 5 + 32;
  }

  set fahrenheit(value) {
    this._celsius = ((value - 32) * 5) / 9;
  }

  get kelvin() {
    return this._celsius + 273.15;
  }

  set kelvin(value) {
    this._celsius = value - 273.15;
  }
}

const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77
console.log(temp.kelvin); // 298.15

temp.fahrenheit = 100;
console.log(temp.celsius); // 37.78 (approximately)

Getter-Only Properties (Read-Only)

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  get radius() {
    return this._radius;
  }

  // No setter - attempting to set will silently fail
  // In strict mode, it throws an error

  get diameter() {
    return this._radius * 2;
  }

  get area() {
    return Math.PI * this._radius ** 2;
  }

  get circumference() {
    return 2 * Math.PI * this._radius;
  }
}

const circle = new Circle(5);
console.log(circle.area.toFixed(2)); // "78.54"
// circle.radius = 10; // Error in strict mode

Computed Method Names

Use expressions for method names using bracket notation.

const methodName = 'dynamicMethod';
const prefix = 'get';

class DynamicClass {
  // Computed method name
  [methodName]() {
    return 'Called dynamic method';
  }

  // Expression as method name
  [`${prefix}Value`]() {
    return 42;
  }

  // Symbol as method name
  [Symbol.iterator]() {
    return this.items[Symbol.iterator]();
  }

  constructor() {
    this.items = [1, 2, 3];
  }
}

const obj = new DynamicClass();
console.log(obj.dynamicMethod()); // "Called dynamic method"
console.log(obj.getValue()); // 42
console.log([...obj]); // [1, 2, 3]

Class Expressions

Classes can be defined as expressions, similar to function expressions.

Anonymous Class Expression

const Animal = class {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound`;
  }
};

const dog = new Animal('Rex');
console.log(dog.speak()); // "Rex makes a sound"

Named Class Expression

const Person = class PersonClass {
  constructor(name) {
    this.name = name;
  }

  // Can reference PersonClass inside the class
  clone() {
    return new PersonClass(this.name);
  }
};

const alice = new Person('Alice');
const aliceClone = alice.clone();

// PersonClass is only accessible inside the class
console.log(typeof Person); // "function"
console.log(typeof PersonClass); // "undefined"

Immediately Invoked Class Expression

const singleton = new (class {
  constructor() {
    this.timestamp = Date.now();
  }

  getTimestamp() {
    return this.timestamp;
  }
})();

console.log(singleton.getTimestamp());

Class as Function Parameter

function createInstance(ClassRef, ...args) {
  return new ClassRef(...args);
}

class User {
  constructor(name) {
    this.name = name;
  }
}

const user = createInstance(User, 'Alice');
console.log(user.name); // "Alice"

Classes vs Constructor Functions

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│              Class vs Constructor Function                   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│      ES6 Class         │      Constructor Function          │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ class Person {         │ function Person(name) {            │
│   constructor(name) {  │     this.name = name;              │
│     this.name = name;  │ }                                  │
│   }                    │                                    │
│                        │ Person.prototype.greet =           │
│   greet() {            │   function() {                     │
│     return "Hi";       │     return "Hi";                   │
│   }                    │   };                               │
│ }                      │                                    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                  Both create same structure:                 │
│                                                             │
│   Person.prototype = {                                      │
│     constructor: Person,                                    │
│     greet: function() { return "Hi"; }                      │
│   }                                                         │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Comparison Table

FeatureClassConstructor Function
HoistingNoYes (declarations)
Strict modeAlwaysOptional
new requiredYes (throws error)No (can call normally)
Method enumerableNoYes (default)
SyntaxCleanerMore verbose
extendsBuilt-inManual setup
superBuilt-inNot available

Under the Hood

class MyClass {
  constructor(value) {
    this.value = value;
  }

  method() {
    return this.value;
  }
}

// Is essentially equivalent to:

function MyClass(value) {
  'use strict';
  if (!new.target) {
    throw new TypeError('Must use new');
  }
  this.value = value;
}

Object.defineProperty(MyClass.prototype, 'method', {
  value: function () {
    'use strict';
    return this.value;
  },
  writable: true,
  configurable: true,
  enumerable: false, // Key difference!
});

Common Patterns

Method Chaining

class QueryBuilder {
  constructor() {
    this.query = {};
  }

  select(fields) {
    this.query.select = fields;
    return this;
  }

  from(table) {
    this.query.from = table;
    return this;
  }

  where(condition) {
    this.query.where = condition;
    return this;
  }

  build() {
    return this.query;
  }
}

const query = new QueryBuilder()
  .select(['name', 'email'])
  .from('users')
  .where({ active: true })
  .build();

Factory Method in Class

class User {
  constructor(name, email, role) {
    this.name = name;
    this.email = email;
    this.role = role;
  }

  // Factory method
  static create(data) {
    return new User(data.name, data.email, data.role || 'user');
  }

  // Factory method for specific type
  static createAdmin(name, email) {
    return new User(name, email, 'admin');
  }
}

const user = User.create({ name: 'Alice', email: 'alice@example.com' });
const admin = User.createAdmin('Bob', 'bob@example.com');

Best Practices

1. Use Meaningful Names

// Good
class ShoppingCart {}
class UserAuthentication {}

// Avoid
class SC {}
class UA {}

2. Keep Constructor Simple

// Good: Simple initialization
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

// Avoid: Heavy logic in constructor
class User {
  constructor(name) {
    this.name = name;
    // Avoid: async operations, API calls, heavy computation
  }
}

3. Use Getters for Computed Values

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  // Good: Computed property
  get area() {
    return this.width * this.height;
  }

  // Avoid: Method for simple computation
  // getArea() { return this.width * this.height; }
}

Summary

ConceptDescription
classDefines a new class
constructorSpecial method for initialization
Instance methodsDefined in class body, shared on prototype
get/setDefine accessor properties
Class expressionClass defined as expression
new requiredClasses must be called with new

Next Steps

  • •Learn about class inheritance with extends
  • •Explore static properties and methods
  • •Understand private and public class fields
  • •Study advanced patterns and mixins
README - JavaScript Tutorial | DeepML