Defining Setter/Getter for an unparented local variable: impossible? - javascript

There's a few previous questions on StackOverflow questioning how one goes about accessing local variables via the scope chain, like if you wanted to reference a local variables using bracket notation and a string, you'd need something like __local__["varName"]. Thus far I haven't found even the hackiest method for accomplishing this, and haven't come up with a method after hours of exploiting every trick I know.
The purpose for it is to implement getters/setters on arbitrary unparented variables. Object.defineProperties or __defineGet/Setter__ require a context to be called on. For properties in the global or window contexts you can accomplish the goal of having a setter/getter for direct references to the object.
Object.defineProperty(this, "glob", {get: function(){return "direct access"})
console.log(glob); //"direct access"
Even in my tests with a custom extension I compiled into a modified Chromium that runs prior to any window creation where the context is the actual global context, and even trying to call this directly in the global context crashes my program, I can pull this off without a hitch:
Object.defineProperty(Object.prototype, "define", {
value: function(name, descriptor){
Object.defineProperty(this, name, descriptor);
}
};
define("REALLYglobal", {get: function(){ return "above window context"; }});
And it is then available in all frames created later as a global routed through the specified getter/setter. The old __defineGet/Setter__ also works in that context without specifying what to call it on (doesn't work in Firefox though, the method above does).
So basically it's possible to define get/set guards for any variable on an object, including the window/global context with direct call to the object (you don't need window.propname, just propname). This is the issue with being unable to reference unparented scoped variables, being the only type that can be in an accessible scope but have no addressable container. Of course they're also the most commonly used too so it's not an edge case. This problem also transcends the current implementation of Proxies in ES6/Harmony since it's a problem specifically with being unable to address a local object's container with the language's syntax.
The reason I want to be able to do this is that it's the only barrier to allow overloading of most math operators for use in complex objects like arrays and hashes and deriving a complex resulting value. I need to be able to hook into the setter in cases where a value is being set on an object type I've set up for overloading. No problem if the object can be global or can be a contained in a parent object, which is probably what I'll just go with. It's still useful with a.myObject, but the goal is to make it as transparently usable as possible.
Not only that, but it'd just be really useful to be able to accomplish something like this:
var point3d = function(){
var x, y, z;
return {
get: function(){ return [x, y, z]; },
set: function(vals){ x=vals[0]; y=vals[1]; z=vals[2]; }
};
};
(That is similar to ES6's destructuring but has more general applications for implementing functionality attached to getting/setting and not just transporting complex values). Even this basic code will completely fail:
var x = {myname: "intercept valueOf and :set: to overload math ops!", index: 5};
x++; //x is now NaN if you don't implement a setter somehow
I don't care how hacky the solution is, at this point it's just an intense curiosity for me as to whether it can be accomplished, even if it requires breaking every best practice that exists. I've crashed Firefox and Chrome a few hundred times in pursuit of this so far by doing things like redefining/intercepting/modifying Object.prototype.valueOf/toString, Function.prototype Function.prototype.constructor, Function.prototype.call/apply, arguments.callee.caller, etc. with infinite recursion errors and whatnot in attempts to jury rig contexts retroactively. The only thing that I've been able to make work is wrapping basically the whole thing with eval and dynamically building code chunks, which is a bridge too far for me to ever actually use. The only other remotely successful route was in using with combined with pre-defining all local variables on a container, but that's obviously very intrusive on top of the issues with using with.

This is currently possible in environments with Proxies. That would be node > 0.6 run as node --harmony_proxies or >0.7 with node --harmony. Chromium Canary (not sure if it's out of that yet) in about:flags at the bottom, experimental javascript. Firefox has had it for a while with no flags.
So this probably won't work when ES6 becomes more official, but it works to an extent now.
var target = (function(){
var handler = Proxy.create(Proxy.create({
get: function(r, trap){
return function(name,val,c,d){
if (trap === 'get' || trap === 'set') {
name = val;
val = c;
}
console.log('"'+trap + '" invoked on property "'+name+'" ' + (val?' with value "'+val+'"':''));
switch (trap) {
case 'get': return target[name];
case 'set': return target[name] = val;
case 'has': return name in target;
case 'delete': return delete target;
case 'keys': return Object.keys(target);
case 'hasOwn': return Object.hasOwnProperty.call(target, name);
case 'getPropertyDescriptor':
case 'getOwnPropertyDescriptor': return Object.getOwnPropertyDescriptor(target, name);
case 'getPropertyNames':
case 'getOwnPropertyNames': return Object.getOwnPropertyNames(target);
case 'defineProperty': return Object.defineProperty(target, name, val);
}
}
}
}))
var target = {
x: 'stuff',
f: { works: 'sure did' },
z: ['overwritten?']
};
with (handler){
var z = 'yes/no';
if (x) {
//x
} else {
x = true;
}
console.log(f.works);
if (f.works) {
f.works = true;
delete f;
}
}
return target
})()
// "getPropertyDescriptor" invoked on property "z"
// "getPropertyDescriptor" invoked on property "z"
// "getPropertyDescriptor" invoked on property "x"
// "get" invoked on property "x"
// "getPropertyDescriptor" invoked on property "console"
// "getPropertyDescriptor" invoked on property "f"
// "get" invoked on property "f"
// sure did
// "getPropertyDescriptor" invoked on property "f"
// "get" invoked on property "f"
// "getPropertyDescriptor" invoked on property "f"
// "get" invoked on property "f"
// "getPropertyDescriptor" invoked on property "f"
target: { x: 'Stuff', f: { works: true }, z: ['overwritten?'] }
Hit or miss and you need to take care not to blow up your browser by simply looking at a Proxy in the debugger. I had to wrap that thing in a closure to keep the proxy from ending up in the global scope or it crashed the frame every single time. Point is that it works to some extent, where nothing else does.

It looks like the answer is No. I have been searching for behavior like this for quite a while. I have not been able to come up with any passable solution. This SO question seems similar. Python has the nice locals keyword.

Since you state you want similar behavior to window/global, I assumed you want this within a given context other that window/global. An easy way to do this is by using the with statement in combination with a local object and a define function which implement Object.defineProperty with local as target. You than simply place your own code within the with block.
IMPORTANT: with overloads the native local variables (var, let, const). Because of this it's very important to keep clear code, and to prevent duplicate names within the scope and parent/child contexts.
Lets start of with the context, in this case I use a closure, but this could also be a function, constructor or any other context.
// This closure represents any function, class or other scoped block.
(function (){
}());
Next we add the storage container and the define function. This is basically what you should always start with if you want access the local properties from anywhere in your code (within this scope).
// This is where we store the local property. (except: var, let, const)
const local = {};
// The define function is used to declare and define the local properties.
function define(name, descriptor){ Object.defineProperty(local, name, descriptor); }
Now you can place any code before the with statement but for this example we'll only add code that requires local in some way so the next step is creating the with statement.
// This with statement extends the current scope with local.
with(local){
// This is where your code goes.
}
Now the outer structure of the with statement is ready, and we can start adding code inside the with statement.
All code placed within the with statement's block has access to the properties of local as if they where defined with for instance var, including properties defined within the with statement.
There are several ways to work with the properties of local. The easiest way to define a property is by setting it within 'local' directly. This only needs to be done once, after that the property is accessable by just it's name.
local.setDirectly = "directly set value";
console.log(setDirectly); // logs "directly set value"
An other way to define a property, but than with support for get/setters as well as options on enumerabiliy and write access, is to use the define function. Expect the same behavior as from Object.defineProperty.
You could for instance add a time property that returns the current time.
define("time", {
get: function(){
var date = new Date();
return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
}
})
console.log(time);
Or you could create a counter property that increments each time it's accessed, placed within a nested closure to protect the counters own variable from unwanted changes.
(function (){
var counterValue = 0;
define("count", {get: function(){ return counterValue++ }});
}());
console.log(count); // logs 0
console.log(count); // logs 1
When you combine all this you will get something similar to the following code
// This closure represeents any function, class or other scoped block.
(function(){
// This is where we store the local property. (except: var, let, const)
const local = {};
// The define function is used to declare and define the local properties.
function define(name, descriptor){ Object.defineProperty(local, name, descriptor); }
// This with statement extends the current scope with local.
with(local){
// This is where your code goes.
// Defining a variable directly into local.
local.setDirectly = "directly set value";
console.log(setDirectly); // logs "directly set value"
// Defining local properties with the define function
// For instance a time variable that return the current time (Hours:Minutes)
define("time", {
get: function(){
var date = new Date();
return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
}
})
console.log(time); // logs HH:MM
// Or a counter property that increments each time it's been accessed.
(function (){
var counterValue = 0;
define("count", {get: function(){ return counterValue++ }});
}());
console.log(count); // logs 0
console.log(count); // logs 1
console.log(count); // logs 2
console.log(count); // logs 3
}
}());
Like I mentioned before, it is important to understand the implications of using the with statement. More information on with can be found at MDN - with. As the question states, it's a search to how you could, not how you should. Use the information on MDN to see if it fits your situation.

I don't know if this answers your question but this works:
Object.defineProperty(window, 'prop', {
get: function () {
alert('you just got me')
},
set: function (val) {
alert('you just set me')
},
configurable: true});

For a solution just with basic objects:
function ref (o)
{
return new Proxy({}, new Proxy({}, {
get (_, prop) { return (_, ...args) => Reflect[prop](o(), ...args) }
}));
}
To work with DOM and primitive objects as well:
function ref (o)
{
return new Proxy({}, new Proxy({}, {
get (_, prop) { return {
get (_, prop) {
let p = o(), r = p[prop];
if (r instanceof Function) r = r.bind(p)
return r
},
set (_, prop, v) { o()[prop] = v },
has (_, prop) { return prop in o() },
keys(_, prop) { return Object.keys(o()) },
apply (_, _this, args) { return Object.apply(o(), _this, args) },
hasOwn (_, prop) { return Object.hasOwnProperty.call(o(), prop) },
ownKeys() {
var p = o();
return Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p))
},
deleteProperty (_, prop) { return delete o()[prop] },
defineProperty (_, prop, desc) { return Object.defineProperty(o(), prop, desc) },
getOwnPropertyDescriptor (_, prop) { return Object.getOwnPropertyDescriptor(o(), prop) }
}[prop] ?? ((_, ...args) => Reflect[prop](o(), ...args));
}}));
}
function refs (o)
{
if (!(o instanceof Function)) o = (o => () => o)(o);
return new Proxy({}, {
get (_, prop) { return ref(() => o()[prop]) }
})
}
Usage
let vec = {x: 0, y: 1, z: 2};
let {x, y, z} = refs(() => vec);
outp(`X: ${x}. Y: ${y}. Z: ${z}`); // X: 0. Y: 1. Z: 2
vec.x = 3;
outp(`X: ${x}. Y: ${y}. Z: ${z}`); // X: 3. Y: 1. Z: 2
x = 1;
outp(vec.x); // 3
vec = {y: 1, z: 1};
outp(y === 1); // false
outp(y == 1); // true
outp(z == 1); // true
outp(y == z); // false
// You cannot directly compare these Proxy objects.
outp(y.valueOf() === z.valueOf()); // true
outp(y.valueOf() === 1); // true
outp(z.valueOf() === 1); // true
function ref (o)
{
return new Proxy({}, new Proxy({}, {
get (_, prop) { return {
get (_, prop) {
let p = o(), r = p[prop];
if (r instanceof Function) r = r.bind(p)
return r
},
set (_, prop, v) { o()[prop] = v },
has (_, prop) { return prop in o() },
keys(_, prop) { return Object.keys(o()) },
apply (_, _this, args) { return Object.apply(o(), _this, args) },
hasOwn (_, prop) { return Object.hasOwnProperty.call(o(), prop) },
ownKeys() {
var p = o();
return Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p))
},
deleteProperty (_, prop) { return delete o()[prop] },
defineProperty (_, prop, desc) { return Object.defineProperty(o(), prop, desc) },
getOwnPropertyDescriptor (_, prop) { return Object.getOwnPropertyDescriptor(o(), prop) }
}[prop] ?? ((_, ...args) => Reflect[prop](o(), ...args));
}}));
}
function refs (o)
{
if (!(o instanceof Function)) o = (o => () => o)(o);
return new Proxy({}, {
get (_, prop) { return ref(() => o()[prop]) }
})
}
let text = '';
function emitText()
{
document.body.appendChild(
Object.assign(document.createElement('pre'), {innerText: text})
);
}
function outp (t)
{
text += " // " + t;
}
function header (t)
{
emitText();
document.body.appendChild(
Object.assign(document.createElement('h1'), {innerText: t})
);
text = '';
}
function log (t)
{
text += '\n' + t;
}
header("Usage");
let vec = {x: 0, y: 1, z: 2}; log('let vec = {x: 0, y: 1, z: 2};');
let {x, y, z} = refs(() => vec); log('let {x, y, z} = refs(() => vec);');
log('outp(`X: ${x}. Y: ${y}. Z: ${z}`);'); outp(`X: ${x}. Y: ${y}. Z: ${z}`);
vec.x = 3; log('vec.x = 3;');
log('outp(`X: ${x}. Y: ${y}. Z: ${z}`);'); outp(`X: ${x}. Y: ${y}. Z: ${z}`);
x = 1; log('x = 1;');
log('outp(vec.x);'); outp(vec.x);
log('');
vec = {y: 1, z: 1}; log('vec = {y: 1, z: 1};');
log('outp(y === 1);'); outp(y === 1);
log('outp(y == 1);'); outp(y == 1);
log('outp(z == 1);'); outp(z == 1);
log('outp(y == z);'); outp(y == z);
log('// You cannot directly compare these Proxy objects.');
log('outp(y.valueOf() === z.valueOf());'); outp(y.valueOf() === z.valueOf());
log('outp(y.valueOf() === 1);'); outp(y.valueOf() === 1);
log('outp(z.valueOf() === 1);'); outp(z.valueOf() === 1);
header('');

Related

Re-use arrow function to write values

If I have an arrow function like:
let fn = x => x.contact.name;
Then, I can re-use that fn to retrieve the value as:
function read(obj) {
return fn(obj)
}
But how can I set values of an object using the property expression in fn?
function write(obj, newVal) {
// intending to set obj.contact.name = newVal using fn
}
Edit:
(To give a little bit of background)
I am writing a JS library where consumers would provide a lambda expression like above (fn), and I provide a read/write functionality somewhere based on the expression. Right now I am using fn.toString() and string manipulation as a temporary solution.
Normally, lenses need two callbacks, get and set, because (in javascript at least) functions are not permitted to return references. For simple cases, like getting/setting a property, you can provide a shortcut function that would return a getter/setter pair. For example:
function prop(path) {
return {
get(obj) {
return path.split('.').reduce((o, p) => o[p], obj)
},
set(val) {
return function (obj) {
let p = path.split('.')
let k = p.pop()
let r = p.reduce((o, p) => o[p], obj)
r[k] = val
}
}
}
}
//
obj1 = { contact: { name: 'one' }}
obj2 = { contact: { name: 'two' }}
let lens = prop('contact.name')
console.log([obj1, obj2].map(lens.get));
[obj1, obj2].forEach(lens.set('hello'));
console.log([obj1, obj2].map(lens.get));
See also: https://randycoulman.com/blog/2016/07/12/thinking-in-ramda-lenses/
The below two functions are almost entirely the same:
let fn = x => x.contact.name;
function fn(x) {
return x.contact.name;
}
As such, you can't use a function that returns an object property to set the object property.
Instead consider the following two functions:
let fn = (x, newVal) => {
if (newVal) x.contact.name = newVal;
return x.contact.name;
};
/* - This function included just for reference -
function fn(x, newVal) {
if (newVal) x.contact.name = newVal;
return x.contact.name;
}
*/
let myObj = { contact: { name: "Jess" } };
console.log(fn(myObj)); // name currently
fn(myObj, "John"); // set new name
console.log(myObj); // show new name on object
Maybe the following will do what you want?
I had to change the "lambda" expression a little bit, as it would not make much sense to give the return value of the original function to a potential "write" function, as it was a value and not a reference. My "new" function definition of fn works differently: it returns an array with two elements:
the "parent" object containing a particular attribute
the name of the attribute of the object.
The functions read() and write() can then pick up the return values of fn(o) and perform their particular actions accordingly.
let fn = x => [x.contact,"name"];
const read=o=>fn(o)[0][fn(o)[1]] // get the attribute as defined in fn
,write=(o,v)=>fn(o)[0][fn(o)[1]]=v; // set the attribute as defined in fn
const o={contact:{name:"Harry",country:"England"}};
console.log(read(o));
write(o,"Hermiony");
console.log(read(o));
// change the lambda function:
fn = x => [x.contact,"country"];
write(o,"Germany");
console.log(read(o));
console.log(o);

Javascript Know when object value is modified

For any variable or property thereof, is there a way to know when its value is set?
For example, say I have:
let x = { 'a': 1, 'b': 2 };
// x.a's set operation is linked to a method
x.a = 3; // the method is automatically called
Is there a way I can call a function when a's value is changed? Lots of code would be changing this value; I don't want to add the method call all over the place.
I'm aware of Proxies, but to use them seems to require a separate variable. Meaning, x can't be a proxy of itself.
Preferably this technique would work with primitive and non-primitives.
x can't be a proxy of itself
Sure it can. You can change the variable to point to a Proxy by simply doing
x = new Proxy(x, handler)
Primitive example:
const handler = {
set: function(obj, prop, value) {
console.log('setting prop: ', prop, ' to ', value)
obj[prop] = value;
return true;
}
};
let x = { 'a': 1, 'b': 2 };
x = new Proxy(x, handler);
x.a = 3; // the method is automatically called
To be honest, use Proxy if you can
If you really can't use Proxy, you could achieve this using using setters and getters
Though it does mean re-declaring your original x object, I assume it's declared inline like the Minimal, Complete and Verifiable Example in your question
let x = {
_a: 1,
_b: 2,
get a() {
return this._a;
},
get b() {
return this._b;
},
set a(value) {
console.log(`changing a from ${this._a} to ${value}`);
this._a = value;
},
set b(value) {
console.log(`changing b from ${this._b} to ${value}`);
this._b = value;
}
};
x.a = 3;

Set undefined javascript property before read

var tr={};
tr.SomeThing='SomeThingElse';
console.log(tr.SomeThing); // SomeThingElse
console.log(tr.Other); // undefined
tr.get=function(what){
if (tr.hasOwnProperty(what)) return tr[what];
else return what;
};
tr.get('SomeThing') // SomeThingElse
tr.get('Other') // Other
Is there any way to make tr.Other or tr['Other'] and all other undefined properties of the object to return its name instead undefined?
Three solutions:
Implement your object as a Proxy, which is designed to do exactly what you want. Yet, it is only a draft and currently only supported in Firefox' Javascript 1.8.5 It was standardised with ES6, but might not yet be available in all environments.
Always fill your translation object with a complete set of messages. When creating that "dictionary" (serverside or clientside), always include all needed keys. If no translation exists, you can use a fallback language, the message's name or the string representation of undefined - your choice.
But a non-existing property should always mean "there is no such message" instead of "no translation available".
Use a getter function with a string parameter instead of object properties. That function can look the messages up in an internal dictionary object, and handle misses programmatically.
I would recommend a map object which is different from the dictionary, to allow "get" and co as message names:
var translate = (function(){
var dict = {
something: "somethingelse",
...
};
return {
exists: function(name) { return name in dict; },
get: function(name) { return this.exists(name) ? dict[name] : "undefined"; },
set: function(name, msg) { dict[name] = msg; }
};
})();
You could define a getter for your property, either using object initializers:
var o = {
a: 7,
get b() {
return this.a + 1;
},
set c(x) {
this.a = x / 2;
}
};
console.log(o.a); // 7
console.log(o.b); // 8 <-- At this point the get b() method is initiated.
o.c = 50; // <-- At this point the set c(x) method is initiated
console.log(o.a); // 25
or using Object.defineProperties():
var o = { a: 0 };
Object.defineProperties(o, {
'b': { get: function() { return this.a + 1; } },
'c': { set: function(x) { this.a = x / 2; } }
});
o.c = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property
console.log(o.b); // Runs the getter, which yields a + 1 or 6
While this solution isn't exactly what you were looking for, a JavaScript implementation of python's collections.defaultdict class might help:
var collections = require('pycollections');
var dd = new collections.DefaultDict([].constructor);
console.log(dd.get('missing')); // []
dd.get(123).push('yay!');
console.log(dd.items()); // [['missing', []], [123, ['yay!']]]

Resume from an error

Before I get yelled at for trying something so reckless, let me tell you that I wouldn't do this in real life and it's an academic question.
Suppose I'm writing a library and I want my object to be able to make up methods as they are needed.
For example if you wanted to call a .slice() method, and I didn't have one then the window.onerror handler would fire it for me
Anyway I played around with this here
window.onerror = function(e) {
var method = /'(.*)'$/.exec(e)[1];
console.log(method); // slice
return Array.prototype[method].call(this, arguments); // not even almost gonna work
};
var myLib = function(a, b, c) {
if (this == window) return new myLib(a, b, c);
this[1] = a; this[2] = b; this[3] = c;
return this;
};
var obj = myLib(1,2,3);
console.log(obj.slice(1));
Also (maybe I should start a new question) can I change my constructor to take an unspecified amount of args?
var myLib = function(a, b, c) {
if (this == window) return new myLib.apply(/* what goes here? */, arguments);
this[1] = a; this[2] = b; this[3] = c;
return this;
};
BTW I know I can load my objects with
['slice', 'push', '...'].forEach(function() { myLib.prototype[this] = [][this]; });
That's not what I'm looking for
As you were asking an academic question, I suppose browser compatibility is not an issue. If it's indeed not, I'd like to introduce harmony proxies for this. onerror is not a very good practice as it's just a event raised if somewhere an error occurs. It should, if ever, only be used as a last resort. (I know you said you don't use it anyway, but onerror is just not very developer-friendly.)
Basically, proxies enable you to intercept most of the fundamental operations in JavaScript - most notably getting any property which is useful here. In this case, you could intercept the process of getting .slice.
Note that proxies are "black holes" by default. They do not correspond to any object (e.g. setting a property on a proxy just calls the set trap (interceptor); the actual storing you have to do yourself). But there is a "forwarding handler" available that routes everything through to a normal object (or an instance of course), so that the proxy behaves as a normal object. By extending the handler (in this case, the get part), you can quite easily route Array.prototype methods through as follows.
So, whenever any property (with name name) is being fetched, the code path is as follows:
Try returning inst[name].
Otherwise, try returning a function which applies Array.prototype[name] on the instance with the given arguments to this function.
Otherwise, just return undefined.
If you want to play around with proxies, you can use a recent version of V8, for example in a nightly build of Chromium (make sure to run as chrome --js-flags="--harmony"). Again, proxies are not available for "normal" usage because they're relatively new, change a lot of the fundamental parts of JavaScript and are in fact not officially specified yet (still drafts).
This is a simple diagram of how it goes like (inst is actually the proxy which the instance has been wrapped into). Note that it only illustrates getting a property; all other operations are simply passed through by the proxy because of the unmodified forwarding handler.
The proxy code could be as follows:
function Test(a, b, c) {
this[0] = a;
this[1] = b;
this[2] = c;
this.length = 3; // needed for .slice to work
}
Test.prototype.foo = "bar";
Test = (function(old) { // replace function with another function
// that returns an interceptor proxy instead
// of the actual instance
return function() {
var bind = Function.prototype.bind,
slice = Array.prototype.slice,
args = slice.call(arguments),
// to pass all arguments along with a new call:
inst = new(bind.apply(old, [null].concat(args))),
// ^ is ignored because of `new`
// which forces `this`
handler = new Proxy.Handler(inst); // create a forwarding handler
// for the instance
handler.get = function(receiver, name) { // overwrite `get` handler
if(name in inst) { // just return a property on the instance
return inst[name];
}
if(name in Array.prototype) { // otherwise try returning a function
// that calls the appropriate method
// on the instance
return function() {
return Array.prototype[name].apply(inst, arguments);
};
}
};
return Proxy.create(handler, Test.prototype);
};
})(Test);
var test = new Test(123, 456, 789),
sliced = test.slice(1);
console.log(sliced); // [456, 789]
console.log("2" in test); // true
console.log("2" in sliced); // false
console.log(test instanceof Test); // true
// (due to second argument to Proxy.create)
console.log(test.foo); // "bar"
The forwarding handler is available at the official harmony wiki.
Proxy.Handler = function(target) {
this.target = target;
};
Proxy.Handler.prototype = {
// Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined
getOwnPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(this.target, name);
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
// Object.getPropertyDescriptor(proxy, name) -> pd | undefined
getPropertyDescriptor: function(name) {
var desc = Object.getPropertyDescriptor(this.target, name);
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
// Object.getOwnPropertyNames(proxy) -> [ string ]
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(this.target);
},
// Object.getPropertyNames(proxy) -> [ string ]
getPropertyNames: function() {
return Object.getPropertyNames(this.target);
},
// Object.defineProperty(proxy, name, pd) -> undefined
defineProperty: function(name, desc) {
return Object.defineProperty(this.target, name, desc);
},
// delete proxy[name] -> boolean
delete: function(name) { return delete this.target[name]; },
// Object.{freeze|seal|preventExtensions}(proxy) -> proxy
fix: function() {
// As long as target is not frozen, the proxy won't allow itself to be fixed
if (!Object.isFrozen(this.target)) {
return undefined;
}
var props = {};
Object.getOwnPropertyNames(this.target).forEach(function(name) {
props[name] = Object.getOwnPropertyDescriptor(this.target, name);
}.bind(this));
return props;
},
// == derived traps ==
// name in proxy -> boolean
has: function(name) { return name in this.target; },
// ({}).hasOwnProperty.call(proxy, name) -> boolean
hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); },
// proxy[name] -> any
get: function(receiver, name) { return this.target[name]; },
// proxy[name] = value
set: function(receiver, name, value) {
this.target[name] = value;
return true;
},
// for (var name in proxy) { ... }
enumerate: function() {
var result = [];
for (var name in this.target) { result.push(name); };
return result;
},
// Object.keys(proxy) -> [ string ]
keys: function() { return Object.keys(this.target); }
};

Function overloading in Javascript - Best practices [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
What is the best way(s) to fake function overloading in Javascript?
I know it is not possible to overload functions in Javascript as in other languages.
If I needed a function with two uses foo(x) and foo(x,y,z) which is the best / preferred way:
Using different names in the first place
Using optional arguments like y = y || 'default'
Using number of arguments
Checking types of arguments
Or how?
The best way to do function overloading with parameters is not to check the argument length or the types; checking the types will just make your code slow and you have the fun of Arrays, nulls, Objects, etc.
What most developers do is tack on an object as the last argument to their methods. This object can hold anything.
function foo(a, b, opts) {
// ...
if (opts['test']) { } //if test param exists, do something..
}
foo(1, 2, {"method":"add"});
foo(3, 4, {"test":"equals", "bar":"tree"});
Then you can handle it anyway you want in your method. [Switch, if-else, etc.]
I often do this:
C#:
public string CatStrings(string p1) {return p1;}
public string CatStrings(string p1, int p2) {return p1+p2.ToString();}
public string CatStrings(string p1, int p2, bool p3) {return p1+p2.ToString()+p3.ToString();}
CatStrings("one"); // result = one
CatStrings("one",2); // result = one2
CatStrings("one",2,true); // result = one2true
JavaScript Equivalent:
function CatStrings(p1, p2, p3)
{
var s = p1;
if(typeof p2 !== "undefined") {s += p2;}
if(typeof p3 !== "undefined") {s += p3;}
return s;
};
CatStrings("one"); // result = one
CatStrings("one",2); // result = one2
CatStrings("one",2,true); // result = one2true
This particular example is actually more elegant in javascript than C#. Parameters which are not specified are 'undefined' in javascript, which evaluates to false in an if statement. However, the function definition does not convey the information that p2 and p3 are optional. If you need a lot of overloading, jQuery has decided to use an object as the parameter, for example, jQuery.ajax(options). I agree with them that this is the most powerful and clearly documentable approach to overloading, but I rarely need more than one or two quick optional parameters.
EDIT: changed IF test per Ian's suggestion
There is no real function overloading in JavaScript since it allows to pass any number of parameters of any type. You have to check inside the function how many arguments have been passed and what type they are.
The correct answer is THERE IS NO OVERLOADING IN JAVASCRIPT.
Checking / Switching inside the function is not OVERLOADING.
The concept of overloading:
In some programming languages, function overloading or method overloading is the ability to create multiple methods of the same name with different implementations. Calls to an overloaded function will run a specific implementation of that function appropriate to the context of the call, allowing one function call to perform different tasks depending on context.
For example, doTask() and doTask(object O) are overloaded methods. To call the latter, an object must be passed as a parameter, whereas the former does not require a parameter, and is called with an empty parameter field. A common error would be to assign a default value to the object in the second method, which would result in an ambiguous call error, as the compiler wouldn't know which of the two methods to use.
https://en.wikipedia.org/wiki/Function_overloading
All suggested implementations are great, but truth to be told, there is no native implementation for JavaScript.
There are two ways you could approach this better:
Pass a dictionary (associative array) if you want to leave a lot of flexibility
Take an object as the argument and use prototype based inheritance to add flexibility.
Here is an approach that allows real method overloading using parameter types, shown below:
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
Edit (2018): Since this was written in 2011, the speed of direct method calls has greatly increased while the speed of overloaded methods have not.
It is not an approach I would recommend, but it is a worthwhile thought exercise to think about how you can solve these types of problems.
Here is a benchmark of the different approaches - https://jsperf.com/function-overloading. It shows that function overloading (taking types into account) can be around 13 times slower in Google Chrome's V8 as of 16.0(beta).
As well as passing an object (i.e. {x: 0, y: 0}), one can also take the C approach when appropriate, naming the methods accordingly. For example, Vector.AddVector(vector), Vector.AddIntegers(x, y, z, ...) and Vector.AddArray(integerArray). You can look at C libraries, such as OpenGL for naming inspiration.
Edit: I've added a benchmark for passing an object and testing for the object using both 'param' in arg and arg.hasOwnProperty('param'), and function overloading is much faster than passing an object and checking for properties (in this benchmark at least).
From a design perspective, function overloading is only valid or logical if the overloaded parameters correspond to the same action. So it stands to reason that there ought to be an underlying method that is only concerned with specific details, otherwise that may indicate inappropriate design choices. So one could also resolve the use of function overloading by converting data to a respective object. Of course one must consider the scope of the problem as there's no need in making elaborate designs if your intention is just to print a name, but for the design of frameworks and libraries such thought is justified.
My example comes from a Rectangle implementation - hence the mention of Dimension and Point. Perhaps Rectangle could add a GetRectangle() method to the Dimension and Point prototype, and then the function overloading issue is sorted. And what about primitives? Well, we have argument length, which is now a valid test since objects have a GetRectangle() method.
function Dimension() {}
function Point() {}
var Util = {};
Util.Redirect = function (args, func) {
'use strict';
var REDIRECT_ARGUMENT_COUNT = 2;
if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
return null;
}
for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
var argsIndex = i-REDIRECT_ARGUMENT_COUNT;
var currentArgument = args[argsIndex];
var currentType = arguments[i];
if(typeof(currentType) === 'object') {
currentType = currentType.constructor;
}
if(typeof(currentType) === 'number') {
currentType = 'number';
}
if(typeof(currentType) === 'string' && currentType === '') {
currentType = 'string';
}
if(typeof(currentType) === 'function') {
if(!(currentArgument instanceof currentType)) {
return null;
}
} else {
if(typeof(currentArgument) !== currentType) {
return null;
}
}
}
return [func.apply(this, args)];
}
function FuncPoint(point) {}
function FuncDimension(dimension) {}
function FuncDimensionPoint(dimension, point) {}
function FuncXYWidthHeight(x, y, width, height) { }
function Func() {
Util.Redirect(arguments, FuncPoint, Point);
Util.Redirect(arguments, FuncDimension, Dimension);
Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
}
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
The best way really depends on the function and the arguments. Each of your options is a good idea in different situations. I generally try these in the following order until one of them works:
Using optional arguments like y = y || 'default'. This is convenient if you can do it, but it may not always work practically, e.g. when 0/null/undefined would be a valid argument.
Using number of arguments. Similar to the last option but may work when #1 doesn't work.
Checking types of arguments. This can work in some cases where the number of arguments is the same. If you can't reliably determine the types, you may need to use different names.
Using different names in the first place. You may need to do this if the other options won't work, aren't practical, or for consistency with other related functions.
If I needed a function with two uses foo(x) and foo(x,y,z) which is the best / preferred way?
The issue is that JavaScript does NOT natively support method overloading. So, if it sees/parses two or more functions with a same names it’ll just consider the last defined function and overwrite the previous ones.
One of the way I think is suitable for most of the case is follows -
Lets say you have method
function foo(x)
{
}
Instead of overloading method which is not possible in javascript you can define a new method
fooNew(x,y,z)
{
}
and then modify the 1st function as follows -
function foo(arguments)
{
if(arguments.length==2)
{
return fooNew(arguments[0], arguments[1]);
}
}
If you have many such overloaded methods consider using switch than just if-else statements.
I'm not sure about best practice, but here is how I do it:
/*
* Object Constructor
*/
var foo = function(x) {
this.x = x;
};
/*
* Object Protoype
*/
foo.prototype = {
/*
* f is the name that is going to be used to call the various overloaded versions
*/
f: function() {
/*
* Save 'this' in order to use it inside the overloaded functions
* because there 'this' has a different meaning.
*/
var that = this;
/*
* Define three overloaded functions
*/
var f1 = function(arg1) {
console.log("f1 called with " + arg1);
return arg1 + that.x;
}
var f2 = function(arg1, arg2) {
console.log("f2 called with " + arg1 + " and " + arg2);
return arg1 + arg2 + that.x;
}
var f3 = function(arg1) {
console.log("f3 called with [" + arg1[0] + ", " + arg1[1] + "]");
return arg1[0] + arg1[1];
}
/*
* Use the arguments array-like object to decide which function to execute when calling f(...)
*/
if (arguments.length === 1 && !Array.isArray(arguments[0])) {
return f1(arguments[0]);
} else if (arguments.length === 2) {
return f2(arguments[0], arguments[1]);
} else if (arguments.length === 1 && Array.isArray(arguments[0])) {
return f3(arguments[0]);
}
}
}
/*
* Instantiate an object
*/
var obj = new foo("z");
/*
* Call the overloaded functions using f(...)
*/
console.log(obj.f("x")); // executes f1, returns "xz"
console.log(obj.f("x", "y")); // executes f2, returns "xyz"
console.log(obj.f(["x", "y"])); // executes f3, returns "xy"
Not everybody knows that you can do Destructuring assignment directly in a function signature.
Thanks to that you can easily define a very flexible method signature, which is, IMHO, superior to Java method overloading.
Example:
const myFunction = (({a, b, c}) => {
console.log(a, b, c);
});
myFunction({a: 1, b: 2});
myFunction({a: 1, b: 2, c: 3});
You don't even need to respect the order of the parameters, and there is naming consistency between the calling statement and the target method signature.
You can also assign default values:
const myFunction = (({a = 1, b = 2, c} = {}) => {
console.log(a, b, c);
});
I just tried this, maybe it suits your needs.
Depending on the number of the arguments, you can access a different function. You initialize it the first time you call it.
And the function map is hidden in the closure.
TEST = {};
TEST.multiFn = function(){
// function map for our overloads
var fnMap = {};
fnMap[0] = function(){
console.log("nothing here");
return this; // support chaining
}
fnMap[1] = function(arg1){
// CODE here...
console.log("1 arg: "+arg1);
return this;
};
fnMap[2] = function(arg1, arg2){
// CODE here...
console.log("2 args: "+arg1+", "+arg2);
return this;
};
fnMap[3] = function(arg1,arg2,arg3){
// CODE here...
console.log("3 args: "+arg1+", "+arg2+", "+arg3);
return this;
};
console.log("multiFn is now initialized");
// redefine the function using the fnMap in the closure
this.multiFn = function(){
fnMap[arguments.length].apply(this, arguments);
return this;
};
// call the function since this code will only run once
this.multiFn.apply(this, arguments);
return this;
};
Test it.
TEST.multiFn("0")
.multiFn()
.multiFn("0","1","2");
INTRODUCTION
So far reading through so many answers would give anyone a headache. Anyone trying to know the concept would need to know the following prerequisites.
Function overloading Definition, Function Length property, Function argument property
Function overloading in its simplest form means that a function performs different tasks on the basis of number of arguments that are being passed to it. Notably the TASK1, TASK2 and TASK3 are highlighted below and are being performed on the basis of the number of arguments being passed to the same function fooYo.
// if we have a function defined below
function fooYo(){
// do something here
}
// on invoking fooYo with different number of arguments it should be capable to do different things
fooYo(); // does TASK1
fooYo('sagar'); // does TASK2
fooYo('sagar','munjal'); // does TAKS3
NOTE
- JS does not provide inbuilt ability of function overloading.
Alternative
John E Resig (creator of JS) has pointed out an alternative which uses the above prerequisites to achieve the ability to implement function overloading.
The code below uses a straightforward but naive approach by using if-else or switch statement.
evaluates the argument-length property.
different values result in invocation of different functions.
var ninja = {
whatever: function() {
switch (arguments.length) {
case 0:
/* do something */
break;
case 1:
/* do something else */
break;
case 2:
/* do yet something else */
break;
//and so on ...
}
}
}
Another technique is much more clean and dynamic. The highlight of this technique is the addMethod generic function.
we define a function addMethod which is used to add different functions to an object with the same name but different functionalities.
below the addMethod function accepts three params object name object, function name name and the function that we want to be invoked fn.
Inside addMethod definition var old stores the reference to the previous function being stored by the help of closure - a protective bubble.
function addMethod(object, name, fn) {
var old = object[name];
object[name] = function(){
if (fn.length == arguments.length)
return fn.apply(this, arguments)
else if (typeof old == 'function')
return old.apply(this, arguments);
};
};
use debugger to understand the code flow.
below the addMethod adds three functions which when invoked using ninja.whatever(x) with the number of arguments x which can be anything i.e. either blank or one or more than one invokes different functions as defined while making use of the addMethod function.
var ninja = {};
debugger;
addMethod(ninja,'whatever',function(){ console.log("I am the one with ZERO arguments supplied") });
addMethod(ninja,'whatever',function(a){ console.log("I am the one with ONE arguments supplied") });
addMethod(ninja,'whatever',function(a,b){ console.log("I am the one with TWO arguments supplied") });
ninja.whatever();
ninja.whatever(1,2);
ninja.whatever(3);
Since JavaScript doesn't have function overload options object can be used instead. If there are one or two required arguments, it's better to keep them separate from the options object. Here is an example on how to use options object and populated values to default value in case if value was not passed in options object.
function optionsObjectTest(x, y, opts) {
opts = opts || {}; // default to an empty options object
var stringValue = opts.stringValue || "string default value";
var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;
return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";
}
here is an example on how to use options object
check this out. It is very cool.
http://ejohn.org/blog/javascript-method-overloading/
Trick Javascript to allow you to do calls like this:
var users = new Users();
users.find(); // Finds all
users.find("John"); // Finds users by name
users.find("John", "Resig"); // Finds users by first and last name
There is no way to function overloading in javascript.
So, I recommend like the following by typeof() method instead of
multiple function to fake overloading.
function multiTypeFunc(param)
{
if(typeof param == 'string') {
alert("I got a string type parameter!!");
}else if(typeof param == 'number') {
alert("I got a number type parameter!!");
}else if(typeof param == 'boolean') {
alert("I got a boolean type parameter!!");
}else if(typeof param == 'object') {
alert("I got a object type parameter!!");
}else{
alert("error : the parameter is undefined or null!!");
}
}
Good luck!
Another way to approach this is by using the special variable: arguments, this is an implementation:
function sum() {
var x = 0;
for (var i = 0; i < arguments.length; ++i) {
x += arguments[i];
}
return x;
}
so you can modify this code to:
function sum(){
var s = 0;
if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
return s;
}
You can now do function overloading in ECMAScript 2018 without polyfills, checking var length/type, etc., just use the spread syntax.
function foo(var1, var2, opts){
// set default values for parameters
const defaultOpts = {
a: [1,2,3],
b: true,
c: 0.3289,
d: "str",
}
// merge default and passed-in parameters
// defaultOpts must go first!
const mergedOpts = {...defaultOpts, ...opts};
// you can now refer to parameters like b as mergedOpts.b,
// or just assign mergedOpts.b to b
console.log(mergedOpts.a);
console.log(mergedOpts.b);
console.log(mergedOpts.c);
console.log(mergedOpts.d);
}
// the parameters you passed in override the default ones
// all JS types are supported: primitives, objects, arrays, functions, etc.
let var1, var2="random var";
foo(var1, var2, {a: [1,2], d: "differentString"});
// parameter values inside foo:
//a: [1,2]
//b: true
//c: 0.3289
//d: "differentString"
What is spread syntax?
The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.
More on mdn
Note: spread syntax in object literals doesn't work in Edge and IE and it is an experimental feature. see browser compatability
#Forwarding Pattern => the best practice on JS overloading
Forward to another function which name is built from the 3rd & 4th points :
Using number of arguments
Checking types of arguments
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)
#Application on your case :
function foo(...args){
return window['foo_' + args.length+'_'+Array.from(args).map((arg)=>typeof arg).join('_')](...args);
}
//------Assuming that `x` , `y` and `z` are String when calling `foo` .
/**-- for : foo(x)*/
function foo_1_string(){
}
/**-- for : foo(x,y,z) ---*/
function foo_3_string_string_string(){
}
#Other Complex Sample :
function foo(...args){
return window['foo_'+args.length+'_'+Array.from(args).map((arg)=>typeof arg).join('_')](...args);
}
/** one argument & this argument is string */
function foo_1_string(){
}
//------------
/** one argument & this argument is object */
function foo_1_object(){
}
//----------
/** two arguments & those arguments are both string */
function foo_2_string_string(){
}
//--------
/** Three arguments & those arguments are : id(number),name(string), callback(function) */
function foo_3_number_string_function(){
let args=arguments;
new Person(args[0],args[1]).onReady(args[3]);
}
//--- And so on ....
As this post already contains a lot of different solutions i thought i post another one.
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function overload() {
var functions = arguments;
var nroffunctionsarguments = [arguments.length];
for (var i = 0; i < arguments.length; i++) {
nroffunctionsarguments[i] = arguments[i].length;
}
var unique = nroffunctionsarguments.filter(onlyUnique);
if (unique.length === arguments.length) {
return function () {
var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);
return functions[indexoffunction].apply(this, arguments);
}
}
else throw new TypeError("There are multiple functions with the same number of parameters");
}
this can be used as shown below:
var createVector = overload(
function (length) {
return { x: length / 1.414, y: length / 1.414 };
},
function (a, b) {
return { x: a, y: b };
},
function (a, b,c) {
return { x: a, y: b, z:c};
}
);
console.log(createVector(3, 4));
console.log(createVector(3, 4,5));
console.log(createVector(7.07));
This solution is not perfect but i only want to demonstrate how it could be done.
You can user the 'addMethod' from John Resig. With this method you can "overload" methods based on arguments count.
// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
var old = object[ name ];
object[ name ] = function(){
if ( fn.length == arguments.length )
return fn.apply( this, arguments );
else if ( typeof old == 'function' )
return old.apply( this, arguments );
};
}
I have also created an alternative to this method that uses caching to hold the variations of the function. The differencies are described here
// addMethod - By Stavros Ioannidis
function addMethod(obj, name, fn) {
obj[name] = obj[name] || function() {
// get the cached method with arguments.length arguments
var method = obj[name].cache[arguments.length];
// if method exists call it
if ( !! method)
return method.apply(this, arguments);
else throw new Error("Wrong number of arguments");
};
// initialize obj[name].cache
obj[name].cache = obj[name].cache || {};
// Check if a method with the same number of arguments exists
if ( !! obj[name].cache[fn.length])
throw new Error("Cannot define multiple '" + name +
"' methods with the same number of arguments!");
// cache the method with fn.length arguments
obj[name].cache[fn.length] = function() {
return fn.apply(this, arguments);
};
}
Function Overloading via Dynamic Polymorphism in 100 lines of JS
VanillaJS, no external dependencies
Full Browser Support - Array.prototype.slice, Object.prototype.toString
1114 bytes uglify'd / 744 bytes g-zipped
This is from a larger body of code which includes the isFn, isArr, etc. type checking functions. The VanillaJS version below has been reworked to remove all external dependencies, however you will have to define you're own type checking functions for use in the .add() calls.
Note: This is a self-executing function (so we can have a closure/closed scope), hence the assignment to window.overload rather than function overload() {...}.
window.overload = function () {
"use strict"
var a_fnOverloads = [],
_Object_prototype_toString = Object.prototype.toString
;
function isFn(f) {
return (_Object_prototype_toString.call(f) === '[object Function]');
} //# isFn
function isObj(o) {
return !!(o && o === Object(o));
} //# isObj
function isArr(a) {
return (_Object_prototype_toString.call(a) === '[object Array]');
} //# isArr
function mkArr(a) {
return Array.prototype.slice.call(a);
} //# mkArr
function fnCall(fn, vContext, vArguments) {
//# <ES5 Support for array-like objects
//# See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility
vArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));
if (isFn(fn)) {
return fn.apply(vContext || this, vArguments);
}
} //# fnCall
//#
function registerAlias(fnOverload, fn, sAlias) {
//#
if (sAlias && !fnOverload[sAlias]) {
fnOverload[sAlias] = fn;
}
} //# registerAlias
//#
function overload(vOptions) {
var oData = (isFn(vOptions) ?
{ default: vOptions } :
(isObj(vOptions) ?
vOptions :
{
default: function (/*arguments*/) {
throw "Overload not found for arguments: [" + mkArr(arguments) + "]";
}
}
)
),
fnOverload = function (/*arguments*/) {
var oEntry, i, j,
a = arguments,
oArgumentTests = oData[a.length] || []
;
//# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
for (i = 0; i < oArgumentTests.length; i++) {
oEntry = oArgumentTests[i];
//# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
for (j = 0; j < a.length; j++) {
if (!oArgumentTests[i].tests[j](a[j])) {
oEntry = undefined;
break;
}
}
//# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
if (oEntry) {
break;
}
}
//# If we found our oEntry above, .fn.call its .fn
if (oEntry) {
oEntry.calls++;
return fnCall(oEntry.fn, this, a);
}
//# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
else {
return fnCall(oData.default, this, a);
}
} //# fnOverload
;
//#
fnOverload.add = function (fn, a_vArgumentTests, sAlias) {
var i,
bValid = isFn(fn),
iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0)
;
//#
if (bValid) {
//# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )
for (i = 0; i < iLen; i++) {
if (!isFn(a_vArgumentTests[i])) {
bValid = _false;
}
}
}
//# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLen
if (bValid) {
oData[iLen] = oData[iLen] || [];
oData[iLen].push({
fn: fn,
tests: a_vArgumentTests,
calls: 0
});
//#
registerAlias(fnOverload, fn, sAlias);
return fnOverload;
}
//# Else one of the passed arguments was not bValid, so throw the error
else {
throw "poly.overload: All tests must be functions or strings referencing `is.*`.";
}
}; //# overload*.add
//#
fnOverload.list = function (iArgumentCount) {
return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);
}; //# overload*.list
//#
a_fnOverloads.push(fnOverload);
registerAlias(fnOverload, oData.default, "default");
return fnOverload;
} //# overload
//#
overload.is = function (fnTarget) {
return (a_fnOverloads.indexOf(fnTarget) > -1);
} //# overload.is
return overload;
}();
Usage:
The caller defines their overloaded functions by assigning a variable to the return of overload(). Thanks to chaining, the additional overloads can be defined in series:
var myOverloadedFn = overload(function(){ console.log("default", arguments) })
.add(function(){ console.log("noArgs", arguments) }, [], "noArgs")
.add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }], "str")
;
The single optional argument to overload() defines the "default" function to call if the signature cannot be identified. The arguments to .add() are:
fn: function defining the overload;
a_vArgumentTests: Array of functions defining the tests to run on the arguments. Each function accepts a single argument and returns truethy based on if the argument is valid;
sAlias (Optional): string defining the alias to directly access the overload function (fn), e.g. myOverloadedFn.noArgs() will call that function directly, avoiding the dynamic polymorphism tests of the arguments.
This implementation actually allows for more than just traditional function overloads as the second a_vArgumentTests argument to .add() in practice defines custom types. So, you could gate arguments not only based on type, but on ranges, values or collections of values!
If you look through the 145 lines of code for overload() you'll see that each signature is categorized by the number of arguments passed to it. This is done so that we're limiting the number of tests we are running. I also keep track of a call count. With some additional code, the arrays of overloaded functions could be re-sorted so that more commonly called functions are tested first, again adding some measure of performance enhancement.
Now, there are some caveats... As Javascript is loosely typed, you will have to be careful with your vArgumentTests as an integer could be validated as a float, etc.
JSCompress.com version (1114 bytes, 744 bytes g-zipped):
window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();
there is no actual overloading in JS, anyway we still can simulate method overloading in several ways:
method #1:
use object
function test(x,options){
if("a" in options)doSomething();
else if("b" in options)doSomethingElse();
}
test("ok",{a:1});
test("ok",{b:"string"});
method #2:
use rest (spread) parameters
function test(x,...p){
if(p[2])console.log("3 params passed"); //or if(typeof p[2]=="string")
else if (p[1])console.log("2 params passed");
else console.log("1 param passed");
}
method #3:
use undefined
function test(x, y, z){
if(typeof(z)=="undefined")doSomething();
}
method #4:
type checking
function test(x){
if(typeof(x)=="string")console.log("a string passed")
else ...
}
Something like this can be done for function overloading.
function addCSS(el, prop, val) {
return {
2: function() {
// when two arguments are set
// now prop is an oject
for (var i in prop) {
el.style[i] = prop[i];
}
},
3: function() {
// when three arguments are set
el.style[prop] = val;
}
}[arguments.length]();
}
// usage
var el = document.getElementById("demo");
addCSS(el, "color", "blue");
addCSS(el, {
"backgroundColor": "black",
"padding": "10px"
});
Source
Function overloading in Javascript:
Function overloading is the ability of a programming language to create multiple functions of the same name with different implementations. when an overloaded function is called it will run function a specific implementation of that function appropriate to the context of the call. This context is usually the amount of arguments is receives, and it allows one function call to behave differently depending on context.
Javascript doesn't have built-in function overloading. However, this behaviour can be emulated in many ways. Here is a convenient simple one:
function sayHi(a, b) {
console.log('hi there ' + a);
if (b) { console.log('and ' + b) } // if the parameter is present, execute the block
}
sayHi('Frank', 'Willem');
In scenarios where you don't know how many arguments you will be getting you can use the rest operator which is three dots .... It will convert the remainder of the arguments into an array. Beware of browser compatibilty though. Here is an example:
function foo (a, ...b) {
console.log(b);
}
foo(1,2,3,4);
foo(1,2);
While Default parameters is not overloading, it might solve some of the issues that developers face in this area. The input is strictly decided by the order, you can not re-order as you wish as in classical overloading:
function transformer(
firstNumber = 1,
secondNumber = new Date().getFullYear(),
transform = function multiply(firstNumber, secondNumber) {
return firstNumber * secondNumber;
}
) {
return transform(firstNumber, secondNumber);
}
console.info(transformer());
console.info(transformer(8));
console.info(transformer(2, 6));
console.info(transformer(undefined, 65));
function add(firstNumber, secondNumber) {
return firstNumber + secondNumber;
}
console.info(transformer(undefined, undefined, add));
console.info(transformer(3, undefined, add));
Results in (for year 2020):
2020
16160
12
65
2021
2023
More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
The Typescript Handbook mentions Overloads. While checking types and doing different logic based on results has been mentioned before, this approach defining multiple named functions to work with a typing system might be interesting to readers. Here is how TypeScript achieves creating a function that accepts multiple types of arguments that will direct the function logic to do different things based on incoming arguments and types:
The answer is to supply multiple function types for the same function as a list of overloads. This list is what the compiler will use to resolve function calls. Let’s create a list of overloads that describe what our pickCard accepts and what it returns.
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
With this change, the overloads now give us type checked calls to the pickCard function.
In order for the compiler to pick the correct type check, it follows a similar process to the underlying JavaScript. It looks at the overload list and, proceeding with the first overload, attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this reason, it’s customary to order overloads from most specific to least specific.
Note that the function pickCard(x): any piece is not part of the overload list, so it only has two overloads: one that takes an object and one that takes a number. Calling pickCard with any other parameter types would cause an error.
We made over.js to solve this problem is a very elegant way. You can do:
var obj = {
/**
* Says something in the console.
*
* say(msg) - Says something once.
* say(msg, times) - Says something many times.
*/
say: Over(
function(msg$string){
console.info(msg$string);
},
function(msg$string, times$number){
for (var i = 0; i < times$number; i++) this.say(msg$string);
}
)
};
This is an old question but one that I think needs another entry (although I doubt anyone will read it). The use of Immediately Invoked Function Expressions (IIFE) can be used in conjunction with closures and inline functions to allow for function overloading. Consider the following (contrived) example:
var foo;
// original 'foo' definition
foo = function(a) {
console.log("a: " + a);
}
// define 'foo' to accept two arguments
foo = (function() {
// store a reference to the previous definition of 'foo'
var old = foo;
// use inline function so that you can refer to it internally
return function newFoo(a,b) {
// check that the arguments.length == the number of arguments
// defined for 'newFoo'
if (arguments.length == newFoo.length) {
console.log("a: " + a);
console.log("b: " + b);
// else if 'old' is a function, apply it to the arguments
} else if (({}).toString.call(old) === '[object Function]') {
old.apply(null, arguments);
}
}
})();
foo(1);
> a: 1
foo(1,2);
> a: 1
> b: 2
foo(1,2,3)
> a: 1
In short, the use of the IIFE creates a local scope, allowing us to define the private variable old to store a reference to the initial definition of the function foo. This function then returns an inline function newFoo that logs the contents of both two arguments if it is passed exactly two arguments a and b or calls the old function if arguments.length !== 2. This pattern can be repeated any number of times to endow one variable with several different functional defitions.
I would like to share a useful example of overloaded-like approach.
function Clear(control)
{
var o = typeof control !== "undefined" ? control : document.body;
var children = o.childNodes;
while (o.childNodes.length > 0)
o.removeChild(o.firstChild);
}
Usage:
Clear(); // Clears all the document
Clear(myDiv); // Clears panel referenced by myDiv
JavaScript is untyped language, and I only think that makes sense to overload a method/function with regards to the number of params. Hence, I would recommend to check if the parameter has been defined:
myFunction = function(a, b, c) {
if (b === undefined && c === undefined ){
// do x...
}
else {
// do y...
}
};

Categories