How to Overload Functions in JavaScript

Function overloading in Javascript - Best practices

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.]

How to overload functions in javascript?

There are multiple aspects to argument overloading in Javascript:

  1. Variable arguments - You can pass different sets of arguments (in both type and quantity) and the function will behave in a way that matches the arguments passed to it.

  2. Default arguments - You can define a default value for an argument if it is not passed.

  3. Named arguments - Argument order becomes irrelevant and you just name which arguments you want to pass to the function.

Below is a section on each of these categories of argument handling.

Variable Arguments

Because javascript has no type checking on arguments or required qty of arguments, you can just have one implementation of myFunc() that can adapt to what arguments were passed to it by checking the type, presence or quantity of arguments.

jQuery does this all the time. You can make some of the arguments optional or you can branch in your function depending upon what arguments are passed to it.

In implementing these types of overloads, you have several different techniques you can use:

  1. You can check for the presence of any given argument by checking to see if the declared argument name value is undefined.
  2. You can check the total quantity or arguments with arguments.length.
  3. You can check the type of any given argument.
  4. For variable numbers of arguments, you can use the arguments pseudo-array to access any given argument with arguments[i].

Here are some examples:

Let's look at jQuery's obj.data() method. It supports four different forms of usage:

obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);

Each one triggers a different behavior and, without using this dynamic form of overloading, would require four separate functions.

Here's how one can discern between all these options in English and then I'll combine them all in code:

// get the data element associated with a particular key value
obj.data("key");

If the first argument passed to .data() is a string and the second argument is undefined, then the caller must be using this form.



// set the value associated with a particular key
obj.data("key", value);

If the second argument is not undefined, then set the value of a particular key.



// get all keys/values
obj.data();

If no arguments are passed, then return all keys/values in a returned object.



// set all keys/values from the passed in object
obj.data(object);

If the type of the first argument is a plain object, then set all keys/values from that object.


Here's how you could combine all of those in one set of javascript logic:

 // method declaration for .data()
data: function(key, value) {
if (arguments.length === 0) {
// .data()
// no args passed, return all keys/values in an object
} else if (typeof key === "string") {
// first arg is a string, look at type of second arg
if (typeof value !== "undefined") {
// .data("key", value)
// set the value for a particular key
} else {
// .data("key")
// retrieve a value for a key
}
} else if (typeof key === "object") {
// .data(object)
// set all key/value pairs from this object
} else {
// unsupported arguments passed
}
},

The key to this technique is to make sure that all forms of arguments you want to accept are uniquely identifiable and there is never any confusion about which form the caller is using. This generally requires ordering the arguments appropriately and making sure that there is enough uniqueness in the type and position of the arguments that you can always tell which form is being used.

For example, if you have a function that takes three string arguments:

obj.query("firstArg", "secondArg", "thirdArg");

You can easily make the third argument optional and you can easily detect that condition, but you cannot make only the second argument optional because you can't tell which of these the caller means to be passing because there is no way to identify if the second argument is meant to be the second argument or the second argument was omitted so what's in the second argument's spot is actually the third argument:

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");

Since all three arguments are the same type, you can't tell the difference between different arguments so you don't know what the caller intended. With this calling style, only the third argument can be optional. If you wanted to omit the second argument, it would have to be passed as null (or some other detectable value) instead and your code would detect that:

obj.query("firstArg", null, "thirdArg");

Here's a jQuery example of optional arguments. both arguments are optional and take on default values if not passed:

clone: function( dataAndEvents, deepDataAndEvents ) {
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

return this.map( function () {
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
});
},

Here's a jQuery example where the argument can be missing or any one of three different types which gives you four different overloads:

html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;

// See if we can take a shortcut and just use innerHTML
} else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

value = value.replace(rxhtmlTag, "<$1></$2>");

try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}

// If using innerHTML throws an exception, use the fallback method
} catch(e) {
this.empty().append( value );
}

} else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery( this );

self.html( value.call(this, i, self.html()) );
});

} else {
this.empty().append( value );
}

return this;
},


Named Arguments

Other languages (like Python) allow one to pass named arguments as a means of passing only some arguments and making the arguments independent of the order they are passed in. Javascript does not directly support the feature of named arguments. A design pattern that is commonly used in its place is to pass a map of properties/values. This can be done by passing an object with properties and values or in ES6 and above, you could actually pass a Map object itself.

Here's a simple ES5 example:

jQuery's $.ajax() accepts a form of usage where you just pass it a single parameter which is a regular Javascript object with properties and values. Which properties you pass it determine which arguments/options are being passed to the ajax call. Some may be required, many are optional. Since they are properties on an object, there is no specific order. In fact, there are more than 30 different properties that can be passed on that object, only one (the url) is required.

Here's an example:

$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
// process result here
});

Inside of the $.ajax() implementation, it can then just interrogate which properties were passed on the incoming object and use those as named arguments. This can be done either with for (prop in obj) or by getting all the properties into an array with Object.keys(obj) and then iterating that array.

This technique is used very commonly in Javascript when there are large numbers of arguments and/or many arguments are optional. Note: this puts an onus on the implementating function to make sure that a minimal valid set of arguments is present and to give the caller some debug feedback what is missing if insufficient arguments are passed (probably by throwing an exception with a helpful error message).

In an ES6 environment, it is possible to use destructuring to create default properties/values for the above passed object. This is discussed in more detail in this reference article.

Here's one example from that article:

function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
};

Then, you can call this like any of these:

selectEntries({start: 5});
selectEntries({start: 5, end: 10});
selectEntries({start: 5, end: 10, step: 2});
selectEntries({step: 3});
selectEntries();

The arguments you do not list in the function call will pick up their default values from the function declaration.

This creates default properties and values for the start, end and step properties on an object passed to the selectEntries() function.

Default values for function arguments

In ES6, Javascript adds built-in language support for default values for arguments.

For example:

function multiply(a, b = 1) {
return a*b;
}

multiply(5); // 5

Further description of the ways this can be used here on MDN.

Method overloading in Javascript

JavaScript does not support method overloading (as in Java or similiar), your third function overwrites the previous declarations.

Instead, it supports variable arguments via the arguments object. You could do

function somefunction(a, b) {
if (arguments.length == 0) { // a, b are undefined
// 1st body
} else if (arguments.length == 1) { // b is undefined
// 2nd body
} else if (arguments.length == 2) { // both have values
// 3rd body
} // else throw new SyntaxError?
}

You also could just check for typeof a == "undefined" etc, this would allow calling somefunction(undefined), where arguments.length is 1. This might allow easer calling with various parameters, e.g. when you have possibly-empty variables.

Function overloading in Javascript - Best practices

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.]

JSDoc - JS overload functions/methods

Do it like the following snippet. Change from function to an arrow function and set its type accordingly. The last thing is to assign a default value to trailing arguments so the overload types are compatible with each other.

/**
* @type {{
* (ticket: string, userid: string): void;
* (ticket: string, firstname: string, lastname: string): void;
* }}
*/
const assignSlave = (a, b, c = '') => {}
assignSlave('ticket', 'userid')
assignSlave('ticket', 'firstname', 'lastname')

This way the function is recognized with 2 overloads:

Overload 1

Overload 1

Overload 2

Overload 2

Function overloading in Javascript - Best practices

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.]

Javascript function overloading and use strict mode

Please see this answer regarding function overloading in JavaScript: Function overloading in Javascript - Best practices. JavaScript does not support overloading in any version of the language, so the "use strict" pragma will not change this behavior.

JavaScript does not use the signature of a function to identify the function. Functions are a specific type of object, and like objects, instances can be passed as parameters or assigned to different variables while remaining the same instance. This is because they are identified based upon their location in memory, and all that is being passed around are references to the memory address of that instance. Each instance remains unique regardless of how it is referenced, and so there is no possibility of overloading a function, because each function declaration creates a new unique instance not related to any other function.

Overloading Functions in JavaScript

There are no overloads in javascript, so feats like this are performed using optional parameters. JavaScript allows you to call functions without specifying parameters. For example:

function val(text)
{
if (arguments != null && arguments.length > 0) {
alert("i'm setting value here to " + text);
} else {
alert("i'm getting value here");
}
}

val(); // alerts value getter
val('hi!'); // alerts value setter and the message

This example was written to point out arguments collection which you get in every function. Every function can fetch it's parameters as a collection and can accordingly perform it's logic. However, displayed example would be written differently in real world:

function val(text)
{
if (typeof text === "string") {
alert("i'm setting value here to " + text);
} else {
alert("i'm getting value here");
}
}

Each parameter which you do not pass to a function gets "value" of undefined (not null). This lets you use the shortest logical expression. You can use any value in if statement. Variables which are null or undefined will simply be evaluated as false.

As a comment below pointed out if text parameter is empty string if (text) would return false. For that reason, for text parameters check parameters by type.

It would be beneficial for you to also read about null and undefined differences.



Related Topics



Leave a reply



Submit