Cómo ejecutar una función a partir de su nombre (ejecutar función por nombre) en JavaScript

No usaremos eval

Eval no es malvado, pero generalmente se malinterpreta , así que para evitar cualquier problema, intente no usar la evalfunción en proyectos (a menos que nadie más que usted la use). Es increíblemente poderoso e increíblemente fácil de abusar de formas que hacen que su código sea más lento y difícil de mantener.

A veces, debido a la simplicidad, querrá ejecutar funciones desde su nombre. Este enfoque se puede utilizar cuando el usuario puede "escribir JavaScript muy limitado" para manipular la página actual o simplemente haciendo un oyente general y global que ejecute una función de acuerdo con el nombre de una función dentro de un atributo:

<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(){
        // En este caso se puede hacer Algo A, B, C
        var callbackName = $(this).data("action");

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

En este artículo, le mostraremos cómo recuperar una función en una variable a partir de su nombre de cadena o ejecutarla directamente en JavaScript.

1. getFunctionByName

Para ejecutar una función de JavaScript en el navegador a partir de su nombre, le recomendamos que utilice la siguiente función getFunctionByName:

/**
 * Devuelve la función que desea ejecutar a través de su nombre.
 * Devuelve indefinido si la función || la propiedad no existe
 * 
 * @param functionName {String} 
 * @param context {Object || null}
 */
function getFunctionByName(functionName, context) {
    // Si usa Node.js, el contexto será un objeto vacío
    if(typeof(window) == "undefined") {
        context = context || global;
    }else{
        // Utilice la ventana (del navegador) como contexto si no se proporciona ninguno.
        context = context || window;
    }

    // Recupere los espacios de nombres de la función que desea ejecutar
    // e.g Namespaces of "MyLib.UI.alerti" would be ["MyLib","UI"]
    var namespaces = functionName.split(".");
    
    // Recupere el nombre real de la función, es decir, alerti
    var functionToExecute = namespaces.pop();
    
     // Itere a través de cada espacio de nombres para acceder al que tiene la función
     // quieres ejecutar. Por ejemplo, con la alerta fn "MyLib.UI.SomeSub.alert"
     // Bucle hasta que el contexto sea igual a SomeSub
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    
    // Si el contexto realmente existe (espacios de nombres), devuelva la función o propiedad
    if(context){
        return context[functionToExecute];
    }else{
        return undefined;
    }
}

Esta función es infalible y no arrojará excepciones de ningún tipo (a menos que no proporcione el primer argumento). ¡Funciona tanto en el navegador como en Node.js!

¿Cómo funciona?

La función comienza con 2 argumentos. El primero es una cadena que representa el espacio de nombres y el nombre de la función a ejecutar, es obligatorio. El segundo argumento es el contexto donde se debe recuperar la función con espacio de nombres (la ventana se usa por defecto si no se proporcionó). Por ejemplo, para usar la getElementByIdfunción, el contexto sería documento, por lo tanto:

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

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

O simplemente podría escribir sin contexto:

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

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

Como la función usa la ventana como contexto cuando no hay ninguna disponible, los dos ejemplos anteriores producirían el mismo resultado. Internamente, la cadena proporcionada se dividirá con un carácter de punto (.) Que indica que cualquier elemento anterior al último es un espacio de nombres (por ejemplo, ventana y documento serían los espacios de nombres de getElementById). Luego, un bucle for iterará a través de cada espacio de nombres y establecerá el contexto interno actual en el último elemento disponible (por ejemplo, IContext = ventana y luego IContext = documento), esto con el fin de evitar una excepción cuando la función no existe, por lo que volverá indefinido:

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

// Pero, si intentas obtener una propiedad de un objeto inexistente, entonces
// obviamente lanzará una excepción:
// Uncaught TypeError: no se puede leer la propiedad 'OtherSub' de undefined
// ya que "getImaginaryFunctionThatDoesnExists" no existe
var imaginaryFN = getFunctionByName("window.document.getImaginaryFunctionThatDoesnExists.OtherSub");

Finalmente, la función devuelve la función si está disponible; de ​​lo contrario, devuelve undefined. El siguiente diagrama muestra cómo funciona esta función internamente:

getFunctionByName JavaScript Schema Description

Cómo utilizar getFunctionByName

De forma predeterminada, getFunctionByName se puede utilizar instantáneamente en el navegador sin necesidad de contexto. Por ejemplo, si desea ejecutar la famosa función de alerta de la ventana desde su nombre de cadena, simplemente puede ejecutar:

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

Alternativamente, puede proporcionar un contexto si necesita:

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

Esto es útil cuando la función que desea ejecutar no está registrada en el espacio de nombres global del navegador. En caso de que esté utilizando una biblioteca que utiliza submétodos, también se puede utilizar sin problemas:

// Dada la siguiente biblioteca imaginaria
window.ThirdPartyLibrary = {
    libraryRegisteredTo: "Our Code World",
    categories: {
        getAllCategories: function(){
            return ["First", "Second"];
        },
        getSingleCategory: function(categorieId){
            return this.getAllCategories()[categorieId];
        }
    }
};


// Muestra en la consola ["First", "Second"]
var getAllCategoriesFN = getFunctionByName("ThirdPartyLibrary.categories.getAllCategories");
console.log(
    getAllCategoriesFN()
);

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

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

2. runFunctionByName

Si solo desea ejecutar la función sin verificar si existe o no, puede usar lo siguiente runFunctionByName:

/**
 * Ejecuta directamente una función desde su nombre con / sin argumentos.
 * 
 * @param functionName {String} 
 * @param context {Object || null}
 */
function runFunctionByName(functionName, context, args) {
    // Si usa Node.js, el contexto será un objeto vacío
    if(typeof(window) == "undefined") {
        context = context || global;
    }else{
        // Utilice la ventana (del navegador) como contexto si no se proporciona ninguno.
        context = context || window;
    }
    
    // Recupere los espacios de nombres de la función que desea ejecutar
    // e.g Namespaces de "MyLib.UI.alerti" seria ["MyLib","UI"]
    var namespaces = functionName.split(".");
    
    // Recupere el nombre real de la función, es decir, alerti
    var functionToExecute = namespaces.pop();
    
     // Itere a través de cada espacio de nombres para acceder al que tiene la función
     // quieres ejecutar. Por ejemplo, con la alerta fn "MyLib.UI.SomeSub.alert"
     // Bucle hasta que el contexto sea igual a SomeSub
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    
    // Si el contexto realmente existe (espacios de nombres), devuelva la función o propiedad
    return context[functionToExecute].apply(context, args);
}

RunFunctionByName funciona de la misma manera que lo hace getFunctionByName, pero en su lugar, la función se ejecuta automáticamente y también su valor devuelto.

Cómo utilizar runFunctionByName

Esta función espera como primer argumento la cadena que representa el espacio de nombres y el nombre de la función a ejecutar, es obligatorio. El segundo argumento es el contexto donde se debe recuperar la función con espacio de nombres (la ventana se usa por defecto si no se proporcionó). Por ejemplo, para usar la getElementByIdfunción, el contexto sería documento, por lo tanto:

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

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

O simplemente podría escribir sin contexto:

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

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

Como la función usa la ventana como contexto cuando no hay ninguna disponible, los dos ejemplos anteriores producirían el mismo resultado. Internamente, la cadena proporcionada se dividirá con un carácter de punto (.) Que indica que cualquier elemento anterior al último es un espacio de nombres (por ejemplo, ventana y documento serían los espacios de nombres de getElementById). Luego, un bucle for iterará a través de cada espacio de nombres y establecerá el contexto interno actual en el último elemento disponible (por ejemplo, IContext = ventana y luego IContext = documento), esto con el fin de evitar una excepción cuando la función no existe, por lo que volverá indefinido. Opcionalmente, como tercer argumento, debe proporcionar una matriz con todos los argumentos que espera la función, por ejemplo:

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

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

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

Entonces se puede usar con funciones reales como:

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

// Normalmente se ejecuta con código
JSON.stringify(dummyData, null, 5);

// Que es equivalente con nuestro método a:
var args = [dummyData, null, 5];

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

Recuerde que runFunctionByName no necesariamente devuelve un valor, por lo que puede usarlo con métodos como la alerta:

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

Que te diviertas ❤️!

Esto podria interesarte

Conviertete en un programador más sociable