Docs
11.1-Class-Basics
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
- ā¢Class Syntax
- ā¢Constructor Method
- ā¢Instance Methods
- ā¢Getter and Setter Methods
- ā¢Computed Method Names
- ā¢Class Expressions
- ā¢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
| Feature | Description |
|---|---|
| Hoisting | Class declarations are NOT hoisted |
| Strict mode | Class body always runs in strict mode |
new required | Cannot call class without new |
| Enumerable | Methods are non-enumerable |
| typeof | typeof 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
| Feature | Class | Constructor Function |
|---|---|---|
| Hoisting | No | Yes (declarations) |
| Strict mode | Always | Optional |
new required | Yes (throws error) | No (can call normally) |
| Method enumerable | No | Yes (default) |
| Syntax | Cleaner | More verbose |
extends | Built-in | Manual setup |
super | Built-in | Not 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
| Concept | Description |
|---|---|
class | Defines a new class |
constructor | Special method for initialization |
| Instance methods | Defined in class body, shared on prototype |
get/set | Define accessor properties |
| Class expression | Class defined as expression |
new required | Classes 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