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 hidden or 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 hidden or 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 |
Author
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-reflectanduniqfrom NPM.If you're familiar with
stampit:initializeris the equivalent ofenclose,extendis the equivalent ofmethods,defaultsis the equivalent ofstate. Getters/setters work as expected and arrays, objects and null-prototype objects in the defaults are cloned on golem creation.