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://www.youtube.com/watch?v=LvjNGo5ALyQ]
https://medium.com/@saif.adnan/typescript-mixin-ee962be3224d
https://egghead.io/lessons/typescript-use-the-optional-chaining-operator-in-typescript
Top comments (0)