Last active
December 26, 2015 21:28
-
-
Save pluma/7215849 to your computer and use it in GitHub Desktop.
ES-harmony multiple inheritance.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require('harmony-reflect'); | |
var uniq = require('uniq'); | |
var arrp = Array.prototype; | |
function merge(obj, src) { | |
Object.keys(src).forEach(function(name) { | |
var value = src[name]; | |
if (obj.hasOwnProperty(name)) { | |
merge(obj[name], value); | |
} else { | |
var proto = Object.getPrototypeOf(value); | |
if (proto === Array.prototype) { | |
obj[name] = [].concat(value); | |
} else if (proto === Object.prototype || proto === null) { | |
obj[name] = merge(Object.create(proto), value); | |
} else { | |
obj[name] = value; | |
} | |
} | |
}); | |
return obj; | |
} | |
function ext(obj) { | |
var sources = arrp.slice.call(arguments, 1); | |
sources.forEach(function(src) { | |
src && Object.keys(src).forEach(function(name) { | |
obj[name] = src[name]; | |
}); | |
}); | |
return obj; | |
} | |
function chained(self, fn) { | |
return function() { | |
fn.apply(this, arrp.slice.call(arguments, 0)); | |
return self; | |
}; | |
} | |
function golem(initial) { | |
var proto = ext(Object.create(null), initial); | |
var parents = []; | |
var defaults = {}; | |
var initializers = []; | |
function factory(state) { | |
var instance = Object.create(proto); | |
var lookups = [instance].concat(parents.map(function(proto) { | |
return proto.create(); | |
})); | |
ext(merge(instance, defaults), state); | |
initializers.forEach(function(initializer) { | |
initializer.call(instance); | |
}); | |
return Proxy(instance, { | |
getOwnPropertyDescriptor: function(target, name) { | |
for (var i = 0; i < lookups.length; i++) { | |
var lookup = lookups[i]; | |
if (lookup.hasOwnProperty(name)) { | |
return Object.getOwnPropertyDescriptor(lookup, name); | |
} | |
} | |
return undefined; | |
}, | |
getOwnPropertyNames: function(target) { | |
return uniq(lookups.map(function(lookup) { | |
return Object.getOwnPropertyNames(lookup); | |
}).reduce(function(a, b) { | |
return [].concat(a, b); | |
}, []).sort()); | |
}, | |
deleteProperty: function(target, name) { | |
lookups.forEach(function(lookup) { | |
delete lookup[name]; | |
}); | |
return true; | |
}, | |
has: function(target, name) { | |
return lookups.map(function(lookup) { | |
return name in lookup; | |
}).reduce(function(a, b) { | |
return a || b; | |
}, false); | |
}, | |
hasOwn: function(target, name) { | |
return lookups.map(function(lookup) { | |
return lookup.hasOwnProperty(name); | |
}).reduce(function(a, b) { | |
return a || b; | |
}, false); | |
}, | |
get: function(target, name) { | |
for (var i = 0; i < lookups.length; i++) { | |
var lookup = lookups[i]; | |
if (name in lookup) { | |
return lookup[name]; | |
} | |
} | |
return undefined; | |
}, | |
enumerate: function(target) { | |
return uniq(lookups.map(function(lookup) { | |
var keys = []; | |
for (var key in lookup) { | |
keys.push(key); | |
} | |
return key; | |
}).reduce(function(a, b) { | |
return [].concat(a, b); | |
}, []).sort()); | |
}, | |
keys: function(target) { | |
return uniq(lookups.map( | |
Object.keys.bind(Object) | |
).reduce(function(a, b) { | |
return [].concat(a, b); | |
}, []).sort()); | |
}, | |
}); | |
} | |
proto.$golem = factory; | |
return ext(factory, { | |
create: factory, | |
mixin: chained(factory, arrp.push.bind(parents)), | |
extend: chained(factory, ext.bind(null, proto)), | |
initializer: chained(factory, arrp.push.bind(initializers)), | |
defaults: chained(factory, ext.bind(null, defaults)), | |
$parents: parents, | |
isInstance: function(obj) { | |
var $golem = obj.$golem; | |
if (!$golem) { | |
return false; | |
} | |
var parents = [$golem]; | |
while (parents.length) { | |
$golem = parents.shift(); | |
if ($golem === factory) { | |
return true; | |
} | |
arrp.push.apply(parents, $golem.$parents); | |
} | |
return false; | |
} | |
}); | |
} | |
module.exports = ext(golem, { | |
compose: function() { | |
var g = golem(); | |
g.mixin.apply(g, arrp.slice.call(arguments, 0)); | |
return g; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var golem = require('golem'); | |
// Usage | |
var foo = golem({f: 'foo'}); | |
var bar = golem({b: 'bar'}); | |
var qux = golem.compose(foo, bar); // or: golem().mixin(foo, bar); | |
var q = qux.create(); | |
var b = bar.create(); | |
b.b; // 'bar' | |
q.f; // 'foo' | |
q.b; // 'bar' | |
bar.isInstance(b); // true | |
bar.isInstance(q); // true | |
qux.isInstance(q); // true | |
qux.isInstance(b); // false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
stampit-ish object factory definitions with real multiple inheritance.
This requires harmony proxies, so it only works with
node --harmony
. Requiresharmony-reflect
anduniq
from NPM.If you're familiar with
stampit
:initializer
is the equivalent ofenclose
,extend
is the equivalent ofmethods
,defaults
is the equivalent ofstate
. Getters/setters work as expected and arrays, objects and null-prototype objects in the defaults are cloned on golem creation.