Learn how to retrieve an execute a function from its string name in the browser or in Node.js

We won't use eval

Eval isn't evil, but its usually misunderstood, so to prevent any problem, try to do not use the eval function in projects (unless no one else but you uses it). It's incredibly powerful and incredibly easy to abuse in ways that make your code slower and harder to maintain.

Sometimes, due to simplicity you will want to execute functions from its name. This approach can be used when the user can "write very limited JavaScript" to manipulate the current page or just by making a general and global listener that executes a function according to the name of a function inside an attribute:

<div>
    <span class="trigger-action" data-action="doSomethingA">Hello</span>
    <span class="trigger-action" data-action="doSomethingB">You awesome</span>
    <span class="trigger-action" data-action="doSomethingC">Person</span>
</div>

<script>
    function doSomethingA(){/* Do some code */}
    function doSomethingB(){/* Do some code */}
    function doSomethingC(){/* Do some code */}

    $(".trigger-action").click(function(){
        // In this case it can be doSomethingA,B,C
        var callbackName = $(this).data("action");

        // dat name :)
        MagicFunctionThatExecutesFunctionFromItsStringName(callbackName);
    });
</script>

In this article we are going to show you how to retrieve a function into a variable from its string name or to execute it directly in JavaScript.

1. getFunctionByName

In order to execute a JavaScript function in the browser from its name, we recommend you to use the following function getFunctionByName:

/**
 * Returns the function that you want to execute through its name.
 * It returns undefined if the function || property doesn't exists
 * 
 * @param functionName {String} 
 * @param context {Object || null}
 */
function getFunctionByName(functionName, context) {
    // If using Node.js, the context will be an empty object
    if(typeof(window) == "undefined") {
        context = context || global;
    }else{
        // Use the window (from browser) as context if none providen.
        context = context || window;
    }

    // Retrieve the namespaces of the function you want to execute
    // e.g Namespaces of "MyLib.UI.alerti" would be ["MyLib","UI"]
    var namespaces = functionName.split(".");
    
    // Retrieve the real name of the function i.e alerti
    var functionToExecute = namespaces.pop();
    
    // Iterate through every namespace to access the one that has the function
    // you want to execute. For example with the alert fn "MyLib.UI.SomeSub.alert"
    // Loop until context will be equal to SomeSub
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    
    // If the context really exists (namespaces), return the function or property
    if(context){
        return context[functionToExecute];
    }else{
        return undefined;
    }
}

This function is foolproof and will not throw exceptions of any kind (unless you don't provide the first argument). It works both in the browser and in Node.js !

How does it works?

The function starts with 2 arguments. The first one is a string that represents the namespace and name of the function to execute, it is required. The second argument is the context where the function with namespace should be retrieved (window is used by default if it wasn't provided). For example, to use the getElementById function, the context would be document, therefore:

var getElementByIdFN = getFunctionByName("getElementById", document);

alert(getElementByIdFN("myTextInput").value);

Or you could simply write without a context:

var getElementByIdFN = getFunctionByName("document.getElementById");

alert(getElementByIdFN("myTextInput").value);

As the function uses the window as context when there's none available, both of previous examples would produce the same result. Internally, the providen string will be splitted by a dot character (.) which indicates that any previous item to the last one is a namespace (e.g window and document would be the namespaces of getElementById). Then a for loop will iterate through every namespace and will set the current internal context to the last available item (e.g IContext = window and then IContext = document), this in order to prevent exception when the function doesn't exist so it will return undefined:

var imaginaryFN = getFunctionByName("window.document.getImaginaryFunctionThatDoesnExists");
// undefined
console.log(typeof(imaginaryFN));

// But, if you try to get a property from a non existent object, then it
// will obviously throw exception:
// Uncaught TypeError: Cannot read property 'OtherSub' of undefined
// as "getImaginaryFunctionThatDoesnExists" doesn't exists
var imaginaryFN = getFunctionByName("window.document.getImaginaryFunctionThatDoesnExists.OtherSub");

Finally the function returns the function if available, otherwise it returns undefined. The following diagram shows how does this function works internally:

getFunctionByName JavaScript Schema Description

How to use getFunctionByName

By default, getFunctionByName can be used instantly in the browser without needing a context. For example, if you want to execute the famous alert function of the window from its string name, you can simply execute:

// Alerts "Hello World !"
var alertFN = getFunctionByName("alert");
alertFN("Hello World !");

Alternatively, you can provide a context if you need to:

// Alerts "Hello World !"
var alertFN = getFunctionByName("alert", window);
alertFN("Hello World !");

This is useful when the function that you want to execute isn't registered on the global namespace of the browser. In case you are using a library that uses submethods, it can be used without a problem too:

// Given the following imaginary library
window.ThirdPartyLibrary = {
    libraryRegisteredTo: "Our Code World",
    categories: {
        getAllCategories: function(){
            return ["First", "Second"];
        },
        getSingleCategory: function(categorieId){
            return this.getAllCategories()[categorieId];
        }
    }
};


// Displays in the console ["First", "Second"]
var getAllCategoriesFN = getFunctionByName("ThirdPartyLibrary.categories.getAllCategories");
console.log(
    getAllCategoriesFN()
);

// Alerts "Second"
var getSingleCategoryFN = getFunctionByName("ThirdPartyLibrary.categories.getSingleCategory");
alert(
    getSingleCategoryFN(1)
);

// Alerts "Our Code World"
alert(
    getFunctionByName("ThirdPartyLibrary.libraryRegisteredTo")
);

2. runFunctionByName

If you only want to run the function without checking if it exist or not, then you can use the following runFunctionByName:

/**
 * Runs directly a function from its name with/without arguments.
 * 
 * @param functionName {String} 
 * @param context {Object || null}
 */
function runFunctionByName(functionName, context, args) {
    // If using Node.js, the context will be an empty object
    if(typeof(window) == "undefined") {
        context = context || global;
    }else{
        // Use the window (from browser) as context if none providen.
        context = context || window;
    }
    
    // Retrieve the namespaces of the function you want to execute
    // e.g Namespaces of "MyLib.UI.alerti" would be ["MyLib","UI"]
    var namespaces = functionName.split(".");
    
    // Retrieve the real name of the function i.e alerti
    var functionToExecute = namespaces.pop();
    
    // Iterate through every namespace to access the one that has the function
    // you want to execute. For example with the alert fn "MyLib.UI.SomeSub.alert"
    // Loop until context will be equal to SomeSub
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    
    // If the context really exists (namespaces), return the function or property
    return context[functionToExecute].apply(context, args);
}

The runFunctionByName works in the same way that the getFunctionByName does, but instead the function is automatically executed and its returned value as well.

How to use runFunctionByName

This function expects as first argument the string that represents the namespace and name of the function to execute, it is required. The second argument is the context where the function with namespace should be retrieved (window is used by default if it wasn't provided). For example, to use the getElementById function, the context would be document, therefore:

var DomElement = runFunctionByName("getElementById", document, ["MyInputId"]);

// alert the value of the input
alert(DomElement.value);

Or you could simply write without a context:

var DomElement = runFunctionByName("document.getElementById", null, ["MyInputId"]);

// alert the value of the input
alert(DomElement.value);

As the function uses the window as context when there's none available, both of previous examples would produce the same result. Internally, the providen string will be splitted by a dot character (.) which indicates that any previous item to the last one is a namespace (e.g window and document would be the namespaces of getElementById). Then a for loop will iterate through every namespace and will set the current internal context to the last available item (e.g IContext = window and then IContext = document), this in order to prevent exception when the function doesn't exist so it will return undefined. Optionally as third argument, you need to provide an array with all the arguments that the function expects, for example:

function Test(text1, text2, text3){
    return text1 + text2 + text3;
}

var args = ["Hello", " ", "World"];
var finalText = runFunctionByName("Test", null, args);

// Alerts "Hello World"
alert(finalText);

So it can be used with real functions like:

var dummyData = {
    hello:"Hey!",
    bye: "Ok bye!",
    id:123
};

// Normally you execute with code
JSON.stringify(dummyData, null, 5);

// Which is equivalent with our method to:
var args = [dummyData, null, 5];

console.log(
    runFunctionByName("JSON.stringify", null, args)
);

Remember that the runFunctionByName doesn't necessarily returns a value, so you can use it with methods like the alert:

// Alerts "Hello Our Code World"
runFunctionByName("alert", null, ["Hello Our Code World"]);

Happy coding !


Senior Software Engineer at Software Medico. Interested in programming since he was 14 years old, Carlos is a self-taught programmer and founder and author of most of the articles at Our Code World.

Sponsors