Skip to content

Instantly share code, notes, and snippets.

@calvinjuarez
Created October 18, 2018 22:09
Show Gist options
  • Save calvinjuarez/702db3f3759e60695e82e97193087421 to your computer and use it in GitHub Desktop.
Save calvinjuarez/702db3f3759e60695e82e97193087421 to your computer and use it in GitHub Desktop.
A Pass at JS Mixins
/**
* Defines a mixin function.
* @param {string} [name='Mixin'] Name to show on Webkit. It will be
* prepended with a `+`, 'cause this
* is my gist.
* @param {function|object} props The mixin itself. If a function
* (class) is passed, its prototype
* will be set to the Base's.
* Otherwise, it will be set as the
* prototype of a new class function.
* @returns {function} The mixin function.
*/
function defineMixin(name, props) { // Chrome ignores the `name`, unfortunately
if (typeof name !== 'string' || ! name) {
if (! props) props = name
name = '+Mixin'
} else {
name = `+${name}`
}
return Base => {
// Chrome exposes this as the identifier in the console when the class isn't
// defined with a name
let Mixin
if (typeof props === 'function') {
Mixin = props
Object.setPrototypeOf(Mixin.prototype, Base.prototype)
} else {
Mixin = class extends Base {}
Object.assign(Mixin.prototype, props)
}
// Webkit exposes this as the identifier in the console when the class isn't
// defined with a name
Object.defineProperty(Mixin, 'name', {value:name, writeable:false})
return Mixin
}
}
// Copy the function defined in defineMixin.js and all the stuff in this file
// into your browser's console and see it work. (Note that I've only tested
// on Chrome 69.0.3497.100 and Safari 12.0 (14606.1.36.1.9).)
let instance
//! BASE CLASS
//! --------------------------------------------------
class Class {
say(message) { console.log(message) }
}
// Note that there are subtle differences between the technical results of the
// following cases, but they all get the job done.
//! STANDARD USE
//! --------------------------------------------------
const mixin = defineMixin('Mixin', class {
sayHello() { this.say('Hello!') }
})
class Subclass extends mixin(Class) {
speak() { this.sayHello() }
}
instance = new Subclass()
instance.speak() // → "Hello!"
//! PASS AN OBJECT LITERAL
//! --------------------------------------------------
const mixinPassingObject = defineMixin('Mixin', {
sayHello() { this.say('Hello from an object literal!') }
})
class SubclassPassingObject extends mixinPassingObject(Class) {
speak() { this.sayHello() }
}
instance = new SubclassPassingObject()
instance.speak() // → "Hello from an object literal!"
//! PASS A NAMED CLASS
//! --------------------------------------------------
const mixinPassingNamedClassOnly = defineMixin(class Mixin {
sayHello() { this.say('Hello from a named class!') }
})
class SubclassPassingNamedClassOnly extends mixinPassingNamedClassOnly(Class) {
speak() { this.sayHello() }
}
instance = new SubclassPassingNamedClassOnly()
instance.speak() // → "Hello from a named class!"
//! PASS AN OBJECT ONLY
//! --------------------------------------------------
const mixinPassingObjectOnly = defineMixin({
sayHello() { this.say('Hello from an (unnamed) object literal!') }
})
class SubclassPassingObjectOnly extends mixinPassingObjectOnly(Class) {
speak() { this.sayHello() }
}
instance = new SubclassPassingObjectOnly()
instance.speak() // → "Hello from an (unnamed) object literal!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment