Last active
January 2, 2016 18:29
-
-
Save Goos/8343814 to your computer and use it in GitHub Desktop.
A quick, dirty and probably slow implmentation of javascript-inheritance with type-checks through custom [gs]etters
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
function realTypeName (obj) { | |
if (obj === null) { | |
return "null"; | |
} | |
if (obj === void(0)) { | |
return "undefined"; | |
} | |
var t = typeof obj; | |
switch(t) { | |
case "function": | |
case "object": | |
if (obj.constructor) { | |
if (obj.constructor.name) { | |
return obj.constructor.name.toLowerCase(); | |
} else { | |
var match = obj.constructor.toString().match(/^function (.+)\(.*$/); | |
if (match) { | |
return match[1]; | |
} | |
} | |
} | |
return baseTypeName(obj); | |
default: | |
return t; | |
} | |
} | |
function baseTypeName (obj) { | |
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() | |
} | |
function isa (val, cl) { | |
var t = typeof(val), cname = cl.name; | |
if (val === null || val === undefined) { | |
return false; | |
} else if (cname === "String") { | |
return baseTypeName(val) === "string"; | |
} else if (cname === "Number") { | |
return baseTypeName(val) === "number"; | |
} else if (cname === "Boolean") { | |
return baseTypeName(val) === "boolean"; | |
} else { | |
return val instanceof cl; | |
} | |
} | |
function extendObject (lobj, robj) { | |
var obj = {}; | |
for (var attrname in lobj) { obj[attrname] = lobj[attrname]; } | |
for (var attrname in robj) { obj[attrname] = robj[attrname]; } | |
return obj; | |
} | |
var protected = /constructor/; | |
function curryMethodTable (prototype, instance) { | |
var curried = {}; | |
for (var propKey in prototype) { | |
if (protected.test(propKey)) { continue; } | |
var property = prototype[propKey]; | |
if (typeof property === "function") { | |
curried[propKey] = property.bind(instance); | |
} | |
} | |
return curried; | |
}; | |
function TypedObject () { | |
var self = this; | |
var pvalues = (typeof arguments[0] === "object") ? arguments[0] : {}; | |
for (var key in self.properties) { | |
var ptype = self.properties[key]; | |
var property = undefined; | |
(function (ptype, property) { | |
var cname = ptype.name.toLowerCase(); | |
Object.defineProperty(self, key, { | |
get: function () { | |
return property; | |
}, | |
set: function (val) { | |
if (isa(val, ptype) || val === undefined || val === null) { | |
property = val; | |
} else { | |
var cl = self.constructor.name; | |
throw new TypeError("Attempted to set property " + cl + "::" + key + " (" + cname + ")" + | |
" to value " + val + " (" + realTypeName(val) + ")"); | |
} | |
}, | |
enumerable: true | |
}); | |
var val = pvalues[key] || undefined; | |
Object.getOwnPropertyDescriptor(self, key).set.call(self, val); | |
})(ptype, property); | |
} | |
return this; | |
} | |
TypedObject.prototype.isKindOfClass = function (cl) { | |
return isa(this, cl); | |
} | |
TypedObject.subclass = function (properties, constructor) { | |
var superclass = this; | |
var subclass = function () { | |
superclass.apply(this, arguments); | |
constructor.apply(this, arguments); | |
this.super = curryMethodTable(superclass.prototype, this); | |
return this; | |
}; | |
subclass.prototype = new superclass(); | |
subclass.prototype.properties = extendObject(properties, superclass.prototype.properties); | |
subclass.prototype.constructor = constructor; | |
subclass.subclass = superclass.subclass; | |
return subclass; | |
}; | |
var Horse = TypedObject.subclass({ name: String }, function Horse () { | |
return this; | |
}); | |
Horse.prototype.neigh = function () { | |
return "My name is " + this.name + " and I'm a silly horse."; | |
}; | |
var Unicorn = Horse.subclass({ awesome: Boolean, birthDate: Date }, function Unicorn () { | |
this.awesome = true; | |
this.birthDate = new Date(); | |
return this; | |
}); | |
Unicorn.prototype.neigh = function () { | |
return this.super.neigh() + " Haha, just kidding, I'm an awesome unicorn"; | |
}; | |
var horseie = new Horse({name: "bob"}); | |
var corny = new Unicorn({name: "john"}); | |
console.log("Is " + horseie.name + " a breed of Horse? " + horseie.isKindOfClass(Horse)); // true | |
console.log("Is " + corny.name + " the unicorn awesome? " + corny.awesome); // true | |
console.log("When was " + corny.name + " born? " + corny.birthDate); // "When was john born? <current date>" | |
console.log("Is " + corny.name + " the unicorn a breed of Horse? " + corny.isKindOfClass(Horse)); // true | |
console.log(horseie.neigh()); // "My name is bob and I'm a silly horse." | |
console.log(corny.neigh()); // "My name is john and I'm a silly horse. Haha, just kidding, I'm an awesome unicorn." | |
console.log("\"What if I prefer to name my unicorns by number instead of a fancy string?\", you ask?") | |
try { | |
corny.name = 5; | |
console.log("Huh.. This should never happen."); | |
} catch (err) { | |
console.log(err); | |
console.log("Well you can't, because this shit be typed, yo."); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment