Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active December 12, 2015 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/4772910 to your computer and use it in GitHub Desktop.
Save dfkaye/4772910 to your computer and use it in GitHub Desktop.
Constructor.js - provide construction prototype and parent inheritance to JavaScript
/**
* 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);
}());
@dfkaye
Copy link
Author

dfkaye commented Mar 31, 2013

This is now implemented (with one or two modifications) at https://github.com/dfkaye/Constructor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment