infamous
Version:
A CSS3D/WebGL UI library.
133 lines (99 loc) • 3.55 kB
JavaScript
import Class from 'lowclass'
export default
function Mixin( factory, Default ) {
factory = Cached( factory )
factory = HasInstance( factory )
factory = Dedupe( factory )
factory = WithDefault( factory, Default || Class() )
factory = ApplyDefault( factory )
return factory()
}
export {
WithDefault,
Cached,
HasInstance,
ApplyDefault,
Dedupe,
}
function WithDefault( classFactory, Default ) {
return Base => {
Base = Base || Default
return classFactory( Base )
}
}
function Cached( classFactory ) {
const classCache = new WeakMap
return Base => {
let Class = classCache.get( Base )
if ( !Class ) {
classCache.set( Base, Class = classFactory( Base ) )
}
return Class
}
}
function HasInstance( classFactory ) {
let instanceofSymbol
return Base => {
const Class = classFactory( Base )
if ( typeof Symbol === 'undefined' || !Symbol.hasInstance )
return Class
if ( Object.getOwnPropertySymbols( Class ).includes( Symbol.hasInstance ) )
return Class
if ( !instanceofSymbol )
instanceofSymbol = Symbol('instanceofSymbol')
Class[instanceofSymbol] = true
Object.defineProperty(Class, Symbol.hasInstance, {
value: function(obj) {
// we do this check because a subclass of `Class` may not have
// it's own `[Symbol.hasInstance]()` method, therefore `this`
// will be the subclass, not this `Class`, when the prototype
// lookup on the subclass finds the `[Symbol.hasInstance]()`
// method of this `Class`. In this case, we don't wsnt to run
// our logic here, so we delegate to the super class of this
// `Class` to take over with the instanceof check. In many
// cases, the super class `[Symbol.hasInstance]()` method will
// be `Function.prototype[Symbol.hasInstance]` which will
// perform the standard check.
if (this !== Class)
// This is effectively a `super` call.
return Object.getPrototypeOf(Class)[Symbol.hasInstance].call(this, obj)
let currentProto = obj
while(currentProto) {
const descriptor = Object.getOwnPropertyDescriptor(currentProto, "constructor")
if (
descriptor &&
descriptor.value &&
descriptor.value.hasOwnProperty(instanceofSymbol)
) return true
currentProto = Object.getPrototypeOf(currentProto)
}
return false
}
})
return Class
}
}
// requires WithDefault or a classFactory that can accept no args
function ApplyDefault( classFactory ) {
const DefaultClass = classFactory()
DefaultClass.mixin = classFactory
return classFactory
}
// requires ApplyDefault
function Dedupe( classFactory ) {
const map = new WeakMap
return Base => {
if ( hasMixin( Base, classFactory, map ) )
return Base
const Class = classFactory( Base )
map.set( Class, classFactory )
return Class
}
}
function hasMixin( Class, mixin, map ) {
while (Class) {
if ( map.get(Class) === mixin ) return true
Class = Class.__proto__
}
return false
}