Как выполнить функцию из ее строкового имени (выполнить функцию по имени) в JavaScript

Мы не будем использовать eval

Эвал не злой, но обычно его неправильно понимают, поэтому, чтобы предотвратить любую проблему, постарайтесь не использовать eval Функция в проектах (если никто, кроме вас, не использует его). Это невероятно мощный и невероятно простой способ злоупотребления способами, которые делают ваш код медленнее и сложнее в обслуживании.

Иногда из-за простоты вы захотите выполнять функции от своего имени. Этот подход можно использовать, когда пользователь может «написать очень ограниченный JavaScript» для манипулирования текущей страницей или просто путем создания общего и глобального слушателя, который выполняет функцию в соответствии с именем функции внутри атрибута:


Hello
You awesome
Person
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);
});

В этой статье мы собираемся показать вам, как извлечь функцию в переменную из ее строкового имени или выполнить ее непосредственно в 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 функция, контекст будет документом, поэтому:

var getElementByIdFN = getFunctionByName("getElementById", document);
alert(getElementByIdFN("myTextInput").value);

Или вы можете просто написать без контекста:

var getElementByIdFN = getFunctionByName("document.getElementById");
alert(getElementByIdFN("myTextInput").value);

Поскольку функция использует окно в качестве контекста, когда его нет, оба предыдущих примера дают одинаковый результат. Внутренне предоставленная строка будет разделена точечным символом (.), Который указывает, что любой предыдущий элемент последнего является пространством имен (например, окно и документ будут пространствами имен 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 функция, контекст будет документом, поэтому:

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

Поскольку функция использует окно в качестве контекста, когда его нет, оба предыдущих примера дают одинаковый результат. Внутренне предоставленная строка будет разделена точечным символом (.), Который указывает, что любой предыдущий элемент последнего является пространством имен (например, окно и документ будут пространствами имен 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"]);
Ссылка на основную публикацию
Adblock
detector