Last active
August 29, 2015 14:24
-
-
Save agostbiro/035b8aaf676f20ebd295 to your computer and use it in GitHub Desktop.
Composing a prototype chain in JavaScript
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
// A demonstration of composing a prototype chain from a list of objects by | |
// appending to the prototype chain of the first object, the rest of the | |
// objects. | |
'use strict'; | |
var assign = Object.assign || require('object.assign'); | |
// Non-enumerable properties are omitted. Property attributes are not respected | |
// and getters and setters aren't copied. Use 'Object.setPrototypeOf' if these | |
// are important. | |
function appendToProto(obj, proto) | |
{ | |
if (Object.prototype.isPrototypeOf(obj) && | |
!Object.prototype.isPrototypeOf(proto)) | |
throw new Error( | |
'Object.prototype will be missing from the resulting proto chain.' | |
); | |
return (function inner(obj, proto) | |
{ | |
if (obj === Object.prototype || obj === null) | |
return proto; | |
else | |
return assign( | |
Object.create( | |
inner(Object.getPrototypeOf(obj), proto) | |
), | |
obj | |
); | |
})(obj, proto); | |
} | |
// Takes a list of objects. The first object will be the farthest away from root | |
// (Object.prototype). | |
function composeProtoChain(prototypes) | |
{ | |
return prototypes.reduce(appendToProto); | |
} | |
// Tests | |
function createA() | |
{ | |
var obj = Object.create({ | |
foo: function foo() | |
{ | |
console.log('foo'); | |
} | |
}); | |
obj.a = 'a'; | |
return obj; | |
} | |
function createB() | |
{ | |
var obj = Object.create({ | |
bar: function bar() | |
{ | |
console.log('bar'); | |
} | |
}); | |
obj.b = 'b'; | |
return obj; | |
} | |
function createC() | |
{ | |
var obj = Object.create({ | |
baz: function baz() | |
{ | |
console.log('baz'); | |
} | |
}); | |
obj.c = 'c'; | |
return obj; | |
} | |
function createBA() | |
{ | |
return Object.create(composeProtoChain([createB(), createA()])); | |
} | |
function createCBA() | |
{ | |
// Since 'createBA' returns an empty object, there will be an empty object | |
// in the prototype chain. | |
return Object.create(composeProtoChain([createC(), createBA()])); | |
} | |
function createCBAOverwrite() | |
{ | |
var | |
a = createA(), | |
b = createB(), | |
c = createC(); | |
a.c = 'this should have no effect'; | |
b.foo = function foo() | |
{ | |
console.log('a.foo overwritten by b'); | |
} | |
c.a = 'a.a overwritten by c'; | |
return Object.create(composeProtoChain([c, b, a])); | |
} | |
function createCBALong() | |
{ | |
return Object.create( | |
composeProtoChain([ | |
createC(), | |
createB(), | |
createA(), | |
createCBA(), | |
createCBAOverwrite() | |
]) | |
); | |
} | |
function test(testee) | |
{ | |
var tester = testee(); | |
console.log(tester.a, tester.b, tester.c); | |
tester.foo && tester.foo(); | |
tester.bar && tester.bar(); | |
tester.baz && tester.baz(); | |
console.log("\n"); | |
} | |
test(createA); | |
/* | |
a undefined undefined | |
foo | |
*/ | |
test(createB); | |
/* | |
undefined 'b' undefined | |
bar | |
*/ | |
test(createC); | |
/* | |
undefined undefined 'c' | |
baz | |
*/ | |
test(createBA); | |
/* | |
a b undefined | |
foo | |
bar | |
*/ | |
test(createCBA); | |
/* | |
a b c | |
foo | |
bar | |
baz | |
*/ | |
test(createCBAOverwrite); | |
/* | |
a.a overwritten by c b c | |
a.foo overwritten by b | |
bar | |
baz | |
*/ | |
test(createCBALong); | |
/* | |
a b c | |
foo | |
bar | |
baz | |
*/ | |
appendToProto({}, {}); | |
appendToProto(Object.create(null), Object.create(null)); | |
appendToProto(Object.create(null), {}); | |
try | |
{ | |
appendToProto({}, Object.create(null)); | |
} | |
catch (e) | |
{ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment