In JavaScript, a Class Expression is an alternative way to define a class. Much like Function Expressions, Class Expressions can be named or unnamed (anonymous). They provide a powerful way to define classes on the fly, pass them as arguments, or assign them to variables and constants.
The primary difference between a Class Expression and a Class Declaration is hoisting: Class Expressions are not hoisted, meaning you cannot use them before they are defined in your code.
1. Anonymous Class Expressions
In an anonymous expression, the class does not have a name after the class keyword. The variable it is assigned to becomes the identifier for the class.
const Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.height * this.width;
}
};
const instance = new Rectangle(10, 5);
console.log(instance.area); // 50
2. Named Class Expressions
You can provide a name to the class expression. This name is local to the class’s body and is primarily useful for recursion or identifying the class in stack traces during debugging.
const User = class UserEntity {
constructor(name) {
this.name = name;
}
whoAmI() {
console.log(UserEntity.name); // Accessing the internal name
}
};
const me = new User("Gemini");
me.whoAmI(); // "UserEntity"
// console.log(UserEntity); // ReferenceError: UserEntity is not defined
3. Dynamic Class Creation (First-Class Citizens)
Because class expressions are values, they can be used in ways that declarations cannot. This allows for powerful patterns like Higher-Order Classes or Class Factories.
A. Classes as Arguments
You can pass a class expression into a function to be used as a blueprint.
function createInstance(ClassDef, name) {
return new ClassDef(name);
}
const person = createInstance(class {
constructor(n) { this.name = n; }
}, "Bob");
B. Class Factories
You can return a class expression from a function. This is often used to generate classes with shared logic based on dynamic input.
function makeClass(phrase) {
return class {
sayIt() {
console.log(phrase);
}
};
}
const Greeter = makeClass("Hello World!");
new Greeter().sayIt(); // "Hello World!"
4. Immediately Invoked Class Expressions (IICE)
Similar to IIFEs (Immediately Invoked Function Expressions), you can define a class and instantiate it immediately. This is useful for creating singletons—objects that represent a unique instance of a specific internal logic.
const singleton = new class {
constructor() {
this.startTime = Date.now();
}
getUptime() {
return Date.now() - this.startTime;
}
}();
console.log(singleton.getUptime());
5. Advanced Patterns and Use Cases
A. Higher-Order Functions (Class Factories)
Because class expressions are just “values” (like strings or numbers), you can return them from functions. This is the basis for the Factory Pattern.
function createModel(tableName) {
return class {
constructor(data) {
this.data = data;
}
save() {
console.log(`Saving to ${tableName}...`);
}
};
}
const User = createModel('users');
const post = new User({ id: 1 });
post.save(); // "Saving to users..."
B. Immediate Class Instantiation (Singleton-ish)
You can create a class expression and instantiate it immediately. This is useful for creating one-off objects that still require the internal logic/encapsulation of a class.
const appConfig = new (class {
constructor() {
this.env = 'production';
}
logEnv() {
console.log(this.env);
}
})();
appConfig.logEnv(); // "production"
C. Mixins
Class expressions allow you to implement Mixins, which are a way to add functionality to classes without using traditional multiple inheritance (which JavaScript doesn’t support).
const CalculatorMixin = (Base) => class extends Base {
calc() { return "Calculating..."; }
};
class Machine {}
class Robot extends CalculatorMixin(Machine) {}
const bot = new Robot();
console.log(bot.calc()); // "Calculating..."
