个性化阅读
专注于IT技术分析

如何在JavaScript中从字符串名称执行函数(按名称执行函数)

本文概述

我们不会使用eval

Eval不是邪恶的, 但通常会被误解, 因此为防止出现任何问题, 请尝试不要在项目中使用eval函数(除非没有人使用, 否则请使用它)。它的功能异常强大, 并且易于滥用, 从而使你的代码变慢且难以维护。

有时, 由于简单起见, 你可能希望从其名称执行功能。当用户可以”编写非常有限的JavaScript”来操纵当前页面时, 或者仅通过使一个通用的全局侦听器根据属性内函数的名称执行函数, 可以使用这种方法:

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

在本文中, 我们将向你展示如何从函数的字符串名称中将函数检索到变量中或直接在JavaScript中执行该函数。

1. getFunctionByName

为了从浏览器名称中执行JavaScript函数, 我们建议你使用以下函数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;
    }
}

该函数是万无一失的, 不会引发任何类型的异常(除非你不提供第一个参数)。它可以在浏览器和Node.js中使用!

如何运作?

该函数以2个参数开头。第一个是一个字符串, 代表要执行的函数的名称空间和名称, 这是必需的。第二个参数是上下文, 在该上下文中应检索具有名称空间的函数(如果未提供, 则默认使用窗口)。例如, 要使用getElementById函数, 上下文将是document, 因此:

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

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

或者你可以简单地在没有上下文的情况下编写:

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

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

由于该函数在没有可用窗口时将窗口用作上下文, 因此上述两个示例将产生相同的结果。在内部, Providen字符串将由点字符(。)分隔, 该点字符表示最后一个项目的前一个项目是名称空间(例如, 窗口和文档将是getElementById的名称空间)。然后, 一个for循环将遍历每个命名空间, 并将当前内部上下文设置为最后一个可用项(例如IContext = window, 然后是IContext = document), 以防止该函数不存在时发生异常, 因此它将返回未定义:

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");

最后, 该函数返回该函数(如果可用), 否则返回未定义。下图显示了此功能在内部如何工作:

getFunctionByName JavaScript架构描述

如何使用getFunctionByName

默认情况下, 不需要上下文就可以在浏览器中立即使用getFunctionByName。例如, 如果要从其字符串名称执行窗口的著名警报功能, 则只需执行以下命令即可:

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

或者, 如果需要, 则可以提供上下文:

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

当你要执行的功能未在浏览器的全局名称空间中注册时, 此功能很有用。如果你使用的是使用子方法的库, 那么也可以毫无问题地使用它:

// 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

如果只想运行该函数而不检查其是否存在, 则可以使用以下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);
}

runFunctionByName的工作方式与getFunctionByName的工作方式相同, 但是该功能会自动执行, 并且其返回值也将自动执行。

如何使用runFunctionByName

该函数要求将代表要执行的函数的名称空间和名称的字符串作为第一个参数。第二个参数是上下文, 在该上下文中应检索具有名称空间的函数(如果未提供, 则默认使用窗口)。例如, 要使用getElementById函数, 上下文将是document, 因此:

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

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

或者你可以简单地在没有上下文的情况下编写:

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

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

由于该函数在没有可用窗口时将窗口用作上下文, 因此上述两个示例将产生相同的结果。在内部, Providen字符串将由点字符(。)分隔, 该点字符表示最后一个项目的前一个项目是名称空间(例如, 窗口和文档将是getElementById的名称空间)。然后, 一个for循环将遍历每个命名空间, 并将当前内部上下文设置为最后一个可用项目(例如IContext = window, 然后是IContext = document), 以防止该函数不存在时发生异常, 因此它将返回未定义。 (可选)作为第三个参数, 你需要提供一个包含该函数期望的所有参数的数组, 例如:

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

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

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

因此, 它可以与诸如:

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)
);

请记住, runFunctionByName不一定返回值, 因此你可以将其与alert之类的方法一起使用:

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

编码愉快!

赞(0)
未经允许不得转载:srcmini » 如何在JavaScript中从字符串名称执行函数(按名称执行函数)

评论 抢沙发

评论前必须登录!