Last active
December 12, 2015 12:38
-
-
Save dfkaye/4772910 to your computer and use it in GitHub Desktop.
Constructor.js - provide construction prototype and parent inheritance to 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
/** | |
* file: Constructor.js - provides construction prototype and parent inheritance to JavaScript | |
* author: @dfkaye - david.kaye | |
* date: 2012-10-30 | |
* | |
* To-DO | |
* - commonjs module support for global scope and exports | |
* - better 'actual' support for extending natives (like Array) - could be bikeshedding, though... | |
* | |
* 11/20/12 | |
* copied over to local | |
* | |
* 12/23/12 | |
* renamed method .create to .extend | |
* made .extend require both args, not just one | |
* re-formatted defn to iife for commonjs To-DONE | |
* | |
*/ | |
;(function (exports) { | |
exports.Constructor = Constructor; | |
/* | |
* constructor function Constructor | |
* | |
* param source - source must be either a function or an object specifier | |
*/ | |
function Constructor(source) { | |
var type = typeof(source); | |
var error = "Constructor(): invalid 'source' argument, must be a function or prototype, but was ";; | |
var ctr; | |
if ('function' == type) { | |
return source; | |
} | |
if ('undefined' === type) { | |
throw new ReferenceError(error + "undefined"); | |
} | |
if ('object' !== type || source === null) { | |
throw new TypeError(error + ('object' != type ? type + " [" + source + "]" : "null")); | |
} | |
ctr = source.hasOwnProperty('constructor') ? source.constructor : function () {}; | |
ctr.prototype = source; | |
ctr.prototype.constructor = ctr; | |
return ctr; | |
}; | |
/* | |
* method Constructor.extend | |
* | |
* param source - required - source must be either a function or an object specifier | |
* param target - required - target must be either a function or an object specifier | |
*/ | |
Constructor.extend = extend; | |
function extend(source, target) { | |
var error = 'Constructor.extend(): '; | |
if (arguments.length < 2) { | |
throw new TypeError(error + 'requires 2 arguments, source and target.'); | |
} | |
var sourceType = typeof(source); | |
var targetType = typeof(target); | |
/* | |
* pass-through if not functions; let Constructor throw errors if not objects either; | |
*/ | |
var newSource = (sourceType !== 'function') ? new Constructor(source) : source; | |
var newConstructor = (targetType !== 'function') ? new Constructor(target) : target; | |
var F = F; | |
var newPrototype; | |
function F() {}; | |
newConstructor.parent = F; | |
F.prototype = newSource.prototype; | |
newPrototype = new F; | |
/* | |
* In order to support the target argument as an object specifier, we have | |
* to take the extra step of copying out its properties onto the new target | |
* function's prototype. | |
*/ | |
if (targetType === 'object') { | |
var proto = newConstructor.prototype; | |
for (var k in proto) { | |
if (proto.hasOwnProperty(k)) { | |
newPrototype[k] = proto[k]; | |
} | |
} | |
} | |
newPrototype.constructor = newConstructor; | |
/* | |
* method parent - a call-once method for initializing the super/parent constructor of | |
* this constructor. parent is replaced with an instance of the super/parent. | |
*/ | |
newPrototype.parent = function () { | |
var parent = this.constructor.parent; | |
var p = new parent; | |
p.constructor.apply(p, arguments); | |
for (var k in p) { | |
if (p.hasOwnProperty(k)) { | |
this[k] = p[k]; | |
} | |
} | |
this.parent = p; | |
return this; | |
}; | |
newConstructor.prototype = newPrototype; | |
return newConstructor; | |
}; | |
}(this)); | |
/*********************************** console test runner ************************************/ | |
(function testContructor() { | |
if (!console) { | |
return; | |
} | |
// test base object | |
function F() {}; | |
console.log('*** positive tests ***'); | |
console.log(new Constructor(F) === F); | |
console.log(Constructor(F) === F); | |
console.log('*** native instances w/new - should all pass ***'); | |
try { Constructor(new Array()); } catch (e) { console.warn(e); } | |
try { Constructor(new Date()); } catch (e) { console.warn(e); } | |
try { Constructor(new String()); } catch (e) { console.warn(e); } | |
try { Constructor(new RegExp()); } catch (e) { console.warn(e); } | |
try { Constructor(new Number()); } catch (e) { console.warn(e); } | |
try { Constructor(new Boolean()); } catch (e) { console.warn(e); } | |
try { Constructor(new XMLHttpRequest()); } catch (e) { console.warn(e); } | |
console.log('*** negative tests ***'); | |
try { console.log(Constructor.extend(F) === F); } catch (e) { console.warn(e); } | |
console.log('*** null tests ***'); | |
try { Constructor(); } catch (e) { console.warn(e); } | |
try { new Constructor(); } catch (e) { console.warn(e); } | |
try { Constructor.extend(); } catch (e) { console.warn(e); } | |
console.log('*** primitives ***'); | |
try { Constructor(''); } catch (e) { console.warn(e); } | |
try { Constructor(3); } catch (e) { console.warn(e); } | |
try { Constructor(true); } catch (e) { console.warn(e); } | |
try { Constructor(false); } catch (e) { console.warn(e); } | |
console.log('*** native instances w/o new ***'); | |
try { Constructor(Array()); } catch (e) { console.warn(e); } | |
try { Constructor(Date()); } catch (e) { console.warn(e); } | |
try { Constructor(String()); } catch (e) { console.warn(e); } | |
try { Constructor(RegExp()); } catch (e) { console.warn(e); } | |
try { Constructor(Number()); } catch (e) { console.warn(e); } | |
try { Constructor(Boolean()); } catch (e) { console.warn(e); } | |
console.log('*** how about Math? ***'); | |
try { Constructor(Math); } catch (e) { console.warn(e); } | |
console.log('*** single argument, object specifier - should fail (12/23/12) ***'); | |
var A; | |
try { | |
A = Constructor.extend({ | |
constructor: function A(name) { | |
this.getName = function getName() { | |
return name; | |
}; | |
}, | |
test: function test() { | |
return this.getName(); | |
} | |
}); | |
} catch (e) { console.warn(e); } | |
console.log('*** should pass from here on ***'); | |
console.log('*** single object arg to Constructor ***'); | |
A = Constructor({ | |
constructor: function A(name) { | |
this.getName = function getName() { | |
return name; | |
}; | |
}, | |
test: function test() { | |
return this.getName(); | |
} | |
}); | |
console.log('*** dual args, both functions, B extending A ***'); | |
var B = Constructor.extend(A, function B(name, value) { | |
this.parent(name); | |
this.getValue = function getValue() { | |
return value; | |
}; | |
}); | |
console.log('*** add method to prototype after the extend() call ***'); | |
B.prototype.test = function test() { | |
return this.getValue() + ":" + this.parent.test(); | |
}; | |
console.log('*** dual args, function B and prototype for C, C extending B ***'); | |
var C = Constructor.extend(B, { | |
constructor: function C(name, value) { | |
this.parent(name, value); | |
}, | |
test: function test() { | |
return this.getName() + ":" + this.getValue() + " >> " + this.parent.test(); | |
} | |
}); | |
console.log('*** dual args, function C and prototype for D, D extending C ***'); | |
var D = Constructor.extend(C, { | |
constructor: function D(name, value, prop) { | |
this.parent(name, value); | |
this.getProp = function getProp() { | |
return prop; | |
} | |
}, | |
test: function test() { | |
return this.getProp() + " >> " + this.parent.test(); | |
} | |
}); | |
console.log('*** various asserts ***'); | |
console.log("typeof A: %s", typeof A); | |
console.log("typeof B: %s", typeof B); | |
console.log("typeof C: %s", typeof C); | |
console.log("typeof D: %s", typeof D); | |
var a = new A('first'); | |
var b = new B('second', '2'); | |
var c = new C('third', '3'); | |
var d = new D('fourth', '4', 'last'); | |
console.log('a.test(): %s', a.test()); | |
console.log('b.test(): %s', b.test()); | |
console.log('c.test(): %s', c.test()); | |
console.log('d.test(): %s', d.test()); | |
console.log("d instanceof A: %s", d instanceof A); | |
console.log("d instanceof B: %s", d instanceof B); | |
console.log("d instanceof C: %s", d instanceof C); | |
console.log("d.parent instanceof C: %s", d.parent instanceof C); | |
console.log("d.parent.parent instanceof B: %s", d.parent.parent instanceof B); | |
console.log("d.parent.parent instanceof A: %s", d.parent.parent instanceof A); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is now implemented (with one or two modifications) at https://github.com/dfkaye/Constructor