A mixin is an abstract subclass; i.e. a subclass definition that may be applied to different superclasses to create a related family of modified classes.
Gilad Bracha and William Cook, Mixin-based Inheritance
mixin definition: The definition of a class that may be applied to different superclasses.
mixin application: The application of a mixin definition to a specific superclass, producing a new subclass.
Mixin libraries like Cocktail, traits.js, and patterns described in many blog posts (like one of the latest to hit Hacker News: Using ES7 Decorators as Mixins), generally work by modifying objects in place, copying in properties from mixin objects and overwriting existing properties.
This is often implemented via a function similar to this:
function mixin(source, target) {
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
A version of this has even made it into JavaScript as Object.assign.
mixin()
is usually then called on a prototype:
mixin(MyMixin, MyClass.prototype);
and now MyClass
has all the properties defined in MyMixin
.
functors: function which creates a function
function myloggerFunction() {
return (str) => {console.log(str)}
}
const logger1 = myloggerFunction();
logger1('hello');
functions that creates class
function myloggerFunction() {
return class MyLoggerClass() {
private
}
}
function myLogFunction() {
return (str: string) => {
console.log(str);
};
}
function myLoggerClass() {
return new (class Logger {
private completeLog: string = "";
log(str: string) {
console.log(str);
this.completeLog += `${str}\n`;
}
dumpLog() {
return this.completeLog;
}
})();
}
function SimpleMemoryDatabase<T>() {
return class SimpleMemoryDatabase {
private db: Record<string, T> = {};
set(id: string, value: T): void {
this.db[id] = value;
}
get(id: string): T {
return this.db[id];
}
getObject(): Record<string, T> {
return this.db;
}
};
}
const StringDatabase = SimpleMemoryDatabase<string>();
const sdb1 = new StringDatabase();
sdb1.set("name", "Jack");
console.log(sdb1.get("name"));
type Constructor<T> = new (...args: any[]) => T;
function Dumpable<
T extends Constructor<{
getObject(): object;
}>
>(Base: T) {
return class Dumpable extends Base {
dump() {
console.log(this.getObject());
}
};
}
const DumpableStringDatabase = Dumpable(StringDatabase);
const sdb2 = new DumpableStringDatabase();
sdb2.set("name", "Jack");
sdb2.dump();
[A mixin is] a function that
takes a constructor,
creates a class that extends that constructor with new functionality
returns the new class
// Needed for all mixins
type Constructor<T = {}> = new (...args: any[]) => T;
////////////////////
// Example mixins
////////////////////
// A mixin that adds a property
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}
// a mixin that adds a property and methods
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActivated = false;
activate() {
this.isActivated = true;
}
deactivate() {
this.isActivated = false;
}
};
}
////////////////////
// Usage to compose classes
////////////////////
// Simple class
class User {
name = '';
}
// User that is Timestamped
const TimestampedUser = Timestamped(User);
// User that is Timestamped and Activatable
const TimestampedActivatableUser = Timestamped(Activatable(User));
////////////////////
// Using the composed classes
////////////////////
const timestampedUserExample = new TimestampedUser();
console.log(timestampedUserExample.timestamp);
const timestampedActivatableUserExample = new TimestampedActivatableUser();
console.log(timestampedActivatableUserExample.timestamp);
console.log(timestampedActivatableUserExample.isActivated);
// Define a simple class with a greet method
class Greeter {
greet(name: string) {
console.log(`Hello, ${name}!`);
}
}
// Define a mixin that adds a log method to a class
type Loggable = { log(message: string): void };
function withLogging<T extends new (...args: any[]) => Loggable>(Base: T) {
return class extends Base {
log(message: string) {
console.log(`[${new Date().toISOString()}] ${message}`);
}
};
}
// Create a new class that combines the Greeter and Loggable mixins
const MyGreeter = withLogging(Greeter);
// Use the new class to create an instance and call its methods
const greeter = new MyGreeter();
greeter.greet("Alice"); // Output: "Hello, Alice!"
greeter.log("An event occurred."); // Output: "[2023-04-04T12:00:00.000Z] An event occurred."
This is just the beginning of many topics related to mixins in JavaScript. I'll post more about things like:
Enhancing Mixins with Decorator Functions new post that covers:
Caching mixin applications so that the same mixin applied to the same superclass reuses a prototype.
Getting
instanceof
to work.How mixin inheritance can address the fear that ES6 classes and classical inheritance are bad for JavaScript.
Using subclass-factory-style mixins in ES5.
De-duplicating mixins so mixin composition is more usable.
References:
https://bryntum.com/blog/the-mixin-pattern-in-typescript-all-you-need-to-know/
https://medium.com/@saif.adnan/typescript-mixin-ee962be3224d
https://egghead.io/lessons/typescript-use-the-optional-chaining-operator-in-typescript