Skip to content

Instantly share code, notes, and snippets.

@Goos
Last active January 2, 2016 18:29
Show Gist options
  • Save Goos/8343814 to your computer and use it in GitHub Desktop.
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
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