Skip to content

Instantly share code, notes, and snippets.

@pgarciacamou
Last active October 27, 2015 19:52
Show Gist options
  • Save pgarciacamou/43990a75788c4d5ad4e6 to your computer and use it in GitHub Desktop.
Save pgarciacamou/43990a75788c4d5ad4e6 to your computer and use it in GitHub Desktop.
Overloading methods in JavaScript like a charm.
// Function that overloads a method
// @param {Object} obj. Object container of the method (can be first-class-object/function)
// @param {String} methodName. Name of the method to overload
// @param {Function} fn. Method that will overload.
// @param {Array} [paramsArr]. Array with the parameters types. OPTIONAL (recommended to always use)
//
// Examples:
// var obj = { method: function(){return "default method";} };
// overloadMethod(obj, "method", function(a){return a;}, ["number"]);
// obj.method(); ==> "default method"
// obj.method("a string"); ==> "default method"
// obj.method(1); ==> 1
//
// Warning: overloadMethod works like a "pile" (LIFO), if there is a method with the same type of parameters and/or the same number of parameters, the last one added will shadow the former method ("override" other methods).
// Warning: if paramsArr is not specified for a specific method, that method will be compared by length of arguments and parameters, meaning that it can easely shadow other methods even if there are parameters specified.
// Note: if method doesn't exist it will make the method passed the default (latest executed/first in the pile).
window.overloadMethod = window.overloadMethod || (function(){
function check(arg, type){
if(typeof type === "string") return typeof arg === type;
if(type === undefined || type === null) return arg === type;
return arg instanceof type;
}
return function overloadMethod(obj, methodName, fn, paramsArr){
// if there wasn't a method before, we make the fn the default method
// ignoring the parameters because it would be the last one being called.
var prev = obj[methodName] || function(){
throw new Error("No default method was set");
};
obj[methodName] = function () {
var args = Array.prototype.slice.call(arguments, 0);
// initial check (faster jump in case the number of arguments differ)
if(args.length > fn.length) return prev.apply(this, args);
// if paramsArr was not passed.
if(!(paramsArr instanceof Array)) {
// if parameters == arguments by length.
if(args.length === fn.length) return fn.apply(this, args);
// if not, continue to the next one.
return prev.apply(this, args);
}
// Compare the type of the arguments agains the parameters Array
// Only if a paramsArray was specified
var isCorrect = true;
paramsArr.forEach(function (param, index) {
// if we already know it is not correct, skip the rest.
if(!isCorrect) return;
var arg = args[index];
// If you can have multiple values. We need to match only one.
if(param instanceof Array){
var valid = false;
param.forEach(function (p, index){
// once is valid, we don't need to check for more.
valid = valid || check(arg, p);
});
if(!valid) isCorrect = false;
} else {
if(!check(arg, param)) isCorrect = false;
}
});
if(isCorrect) return fn.apply(this, args);
else return prev.apply(this, args);
};
};
})();
// Function that returns an overloaded set of functions.
// Better for when overloading in a prototype definition (see example files).
// @param {Object} obj. Objects separated by commas.
// @prop {Array} [params]. Array of strings specifying the type of parameters. OPTIONAL (recommended to always use)
// @prop {Function} value. Function that will be exectued when the parameters match.
//
// Examples:
// function Constructor(){}
// Constructor.prototype.foo = overload({
// params:["string"],
// value: function () { ... }
// },{
// params:["number"],
// value: function () { ... }
// });
window.overload = window.overload || function overload(){
// Create a dummy object to hold the overloaded methods
var foo = {};
var bar = "bar";
// For each argument
Array.prototype.slice.call(arguments, 0).forEach(function (overloadObj) {
window.overloadMethod(foo, bar, overloadObj.value, overloadObj.params);
});
// returns the overloaded set
return foo[bar];
};
// Simple example
var obj = {
method: (a) => {
console.log(a+1);
}
};
obj.method(1); // 2
obj.method("hola"); // hola1
// Overloading
overloadMethod(obj, "method", (a) => {
console.log("string --> " + a);
}, ["string"]);
obj.method(1); // 2
obj.method("hola"); // string --> hola
// Example Constructor to show that this will apply the overload even to the prototype.
function Constructor(){}
Constructor.prototype = {
constructor: Constructor,
method: (a) => {
console.log(a);
}
};
// Instance:
var c = new Constructor();
// (1) Default:
c.method(1); // 1
// (2) This will actually shadow the default method because both methods have the same number of parameters:
overloadMethod(Constructor.prototype, "method", (a) => {
console.log(a+1);
});
c.method(1); // 2
// (3) Right way to overload:
overloadMethod(Constructor.prototype, "method", (a) => {
console.log(a);
}, ["function"]);
c.method(1); // 2 :: because we shadow it with the method #(2).
c.method(()=>{return "test";}); // function(){return "test";}
// (4) Yet another example of overloading:
// Right way to overload:
overloadMethod(Constructor.prototype, "method", (a, b) => {
console.log(a, b);
}, ["function", "number"]);
c.method(1); // 2 :: because we shadow it with the method #(2).
c.method(()=>{return "test";}); // function(){return "test";} :: this access method #(3)
c.method(()=>{return "test";}, 1); // function(){return "test";} && 1 :: this access method #(4)
function Foo(name){
this.name = name;
}
Foo.prototype = {
constructor: Foo,
mult: overload({
params: ["number"],
value: function(x) {
return x*x;
}
},{
params: ["number", "number"],
value: function (x,y) {
return x*y;
}
}),
whatsYourName: overload({
params: [],
value: function () {
return this.name;
}
},{
params: ["string"],
value: function (s) {
return s + " " + this.name;
}
})
};
var foo = new Foo("foo instance");
console.log(foo.mult(2)); // 4
console.log(foo.mult(2,3)); // 6
console.log(foo.whatsYourName()); // foo instance
console.log(foo.whatsYourName("hola")); // hola foo instance
// This examples shows overloading with multiple types per parameter.
function Foo(name){
this.name = name;
}
Foo.prototype = {
constructor: Foo,
mult: overload({
value: function (x,y) {
throw new Error("it shouldn't get here");
}
},{
params: [["number", "string", Array], ["number", "string", undefined]],
value: function(x, y) {
return x;
}
})
};
var foo = new Foo("foo instance");
console.log(foo.mult(2)); // 2
console.log(foo.mult("hola")); // "hola"
console.log(foo.mult([2])); // [2]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment