Запуск функции по значению переменной — javascript функции

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд
Загрузка...

Вопрос:


Когда-то встречал функцию, но забыл, чтобы по значению элемента вызвать одноимённую функцию, не используя switch-case.
Например, a="add_New_User" или a="Delete_User" и есть одноимённые функции.

Как их запустить, в зависимости от значения переменной а?

Автор вопроса: Rakzin Roman

Итак, этим ответом я постараюсь наконец исчерпать данную тему, которая постоянно всплывает на (ru)SO


#0. Eval

Метод eval() выполняет JavaScript код, представленный строкой.

Из определения понятно, что его использование крайне просто.
Касательно поставленной задачи это будет выглядеть так:

function invokeMe() {
    return "Hello world!";
}

var result = eval("invokeMe()");
console.log(result);

Важным отличием eval() от иных подходов является то, что выражение, переданное в качестве строки, может ссылаться на текущий контекст:

function localContext() {
    let x = 3;
    let y = 4;
    // eval ссылается на текущий контекст,
    // так что локальные переменные x и y ему видны
    return eval("x * y");
}

function globalContext() {
    let x = 3;
    let y = 4;
    // создадим ссылку на eval
    var globalEval = eval;
    try {
        // так как вызов не является прямым,
        // то переменные x и y недоступны для eval!
        return globalEval ("x * y");
    } catch (e) {
        // что мы и увидим благодаря этой строчке
        return "Error: " + e.message;
    }
}

console.log(localContext());  // 12
console.log(globalContext()); // Error: x is not defined

С использованием, думаю, все понятно.

Однако каждый из нас не один раз уже видел высказывания в духе:

eval() (читать как evil)

eval() — зло!

И далее по списку.
Почему о данной функции бытует такое мнение?

eval() — опасная функция, которая выполняет код, проходящий со всеми привилегиями вызывателя. Если вы запускаете eval() со строкой, на которую могут влиять злоумышленники, то вы можете запустить вредоносный код на устройство пользователя с правами вашей веб-страницы/расширения.

Так что использовать данный метод для серьезных целей не стоит.


#1. Function

Когда речь заходит об eval(), не обойдется и без упоминания о new Function()

Конструктор Function создаёт новый объект Function. В JavaScript
каждая функция является объектом Function.

Стоит отметить следующие важные отличия от прошлого подхода:

  1. В отличие от eval(), при помощи new Function() мы не просто
    исполняем код из строки, а мы создаем из него полноценную функцию,
    аналогичную по свойствам тем, что мы определилили через function() { } (так как глобальный объект Function наследует некоторые
    методы и свойства через цепочку прототипов объекта
    Function.prototype), которая может принимать входные параметры, а также может быть переиспользована
  2. В отличие от eval(), функции, созданные с помощью new Function(), не могут ссылаться на область видимости, в которой они
    были созданы, так как при вызове они имеют доступ только к
    глобальным и своим внутренним переменным!

  3. В отличие от eval(), возвращается не последнее вычисленное в теле
    функции выражение, а то, которое было явно передано с помощью оператора
    return (ну, или undefined. В общем, поведение обычной функции, так как это она и есть ¯_(ツ)_/¯)

Синтаксис:

new Function([arg1[, arg2[, …argN]],] functionBody)

С теорией, думаю, ясно: конструктор new Function(), в который были переданы имена формальных аргументов и обязательный параметр — строковое представление тела метода, создает нам, собственно, функцию

Касательно поставленной задачи:

// Функция с параметром
function invokeMe(name) {
    return "Hello " + name + "!";
}

// Функция без параметров
function helloWorld() {
    return invokeMe("world");
}

// Определим функцию, которая будет возвращать результат
// исполнения метода helloWorld()
var world = new Function("return helloWorld();");

// Определим функцию, которая будет возвращать результат
// исполнения метода invokeMe(), передавая в него параметр name
var named = new Function("name", "return invokeMe(name);");

// Определим функцию, которая будет возвращать результат
// исполнения метода invokeMe(), передавая в него
// нулевой элемент объекта arguments, который, как и в обычных
// функциях, можно использовать внутри тела функции
var named2 = new Function("return invokeMe(arguments[0]);");

// Проверим)
console.log(world());         // Hello world!
console.log(named("Vasya"));  // Hello Vasya!
console.log(named2("Petya")); // Hello Petya!

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


#2. Получение доступа к объекту, как к свойству контейнера с помощью скобочной нотации

Думаю, все мы знаем, что к свойству объекта можно обратиться несколькими путями.
Простенький пример:

let test = {
    name: "Test",
    sayHello: () => console.log("Hello!")
};

// Обратимся к свойству name объекта test:

// Самый очевидный вариант - сделать так:
console.log(test.name); // "Test"

// Однако мы можем сделать это и с помощью скобочной нотации:
console.log(test["name"]) // "Test"



// Теперь же вызовем внутреннюю функцию sayHello объекта test:

// И вновь самым очевидным выбором будет:
test.sayHello();

// Однако опять же нам может помочь скобочная нотация:
test["sayHello"]();

Как можно применить это касательно поставленной задачи?
Я уже затронул это в сниппете выше, но давайте рассмотрим это дело чуть подробнее:

// Определим глобальную функцию
function helloFromGlobal() {
    console.log("Hello from outer function!");
}

// Определим объект, свойством которого будет функция
var obj = {
    helloFromLocal: () => console.log("Hello from inner function!")
};

// Как же получить доступ к глобальной функции?
// Все просто, ведь контейнером для нее является объект window
// (как и для любого другого глобального объекта)
let outer = window["helloFromGlobal"];

// А как же получить доступ ко внутренней функции объекта,
// если его имя доступно нам только в качестве строки?
// Опять же, объект является глобальным, так что:
let inner = window["obj"]["helloFromLocal"];

// Проверим, что мы получили именно наши функции,
// а не кота в мешке в лице undefined:
outer(); // Hello from outer function!
inner(); // Hello from inner function!

Данный метод можно считать одним из наиболее правильных при решении поставленной задачи, ведь скобочная и точечная нотация взаимозаменяемы и их поведение полностью документировано, а также при помощи скобочной нотации злоумышленнику вряд ли удастся запустить какой-либо (свой) вредоносный код

В комментариях упоминалось, что может потребоваться запустить функцию, путь к которой описан следующим образом: myObj.method.myFunc.
Ответ прост: разделим строку по точке и пройдем весь этот путь, описанный точечной нотацией, с помощью скобочной:

// "Путь" к функции
var func = 'myObj.method.myFunc';

// Опишем объект
var myObj = {
    method: {
        myFunc: () => console.log("I was here!")
    }
};

// Получим "путь", разделив точечную нотацию
var path = func.split('.');

// Присвоим переменной значение "верхнего" уровня
var myFunc = window;

// Начнем спускаться глубже и глубже, пока не дойдем до функции
for (i in path)
    myFunc = myFunc[path[i]];

// Проверка
myFunc(); // I was here!

Дешево, сердито и надежно!


#3. Создание «словаря»

Еще одним неплохим вариантом может явиться способ, идея которого уже представлена на текущий момент в ответе участника @qwabra:

Суть заключается в том, что Вам необходимо создать некоторый объект-«словарь», в который Вы сможете добавлять функции, а после — вызывать их по имени.

Для примера набросал такой код:

// Определим нашу конструкцию, которая будет отвечать
// за хранение и вызов функций
var funcs = new function() {
    // Хранилище
    let funcs = [];

    // Добавление функции в хранилище
    this.push = function() {
        // Добавление функции с использованием ее названия
        if (arguments.length == 1 && arguments[0] && arguments[0].name) 
            return !!(funcs[arguments[0].name] = arguments[0]);
        
        // Добавление функции с использованием указанного названия
        else if (arguments.length == 2 && arguments[0] && arguments[1]) 
            return !!(funcs[arguments[0]] = arguments[1]);

        // Добавление не удалось
        return false;
    };

    // Вызов функции с указанными аргументами
    this.run = function(name, ...args) {
        if (name && name in funcs)
            return funcs[name](...args);

        // Вызов не удался
        return undefined;
    };

    // Удаление функции из хранилища
    this.remove = function(name) {
        if (name && name in funcs)
            return delete funcs[name];

        // Функции итак у нас не было)
        return undefined;
    };
};


// Создадим функции

// Без параметров
function helloWorld() {
    console.log("Hello world!");
}

// С параметрами
function add(...args) {
    var sum = 0;
    for (i in args) sum += Number(args[i]); 
    return sum;
}

// Добавим их в хранилище
console.log(funcs.push(helloWorld)); // true
console.log(funcs.push(add));        // true

// Определим стрелочную функцию
var sub = (...args) => {
    if (args.length > 0)
    {
        var sub = args[0] * 2;
        for (i in args) sub -= args[i];
        return sub;
    }
    return 0;
};

// Попробуем добавить стрелочную функцию в хранилище
console.log(funcs.push(sub)); // true

// Попробуем добавить безымянную функцию
console.log(funcs.push(() => console.log("Hi!")));       // false

// Попробуем теперь явно указать ее имя
console.log(funcs.push("hi", () => console.log("Hi!"))); // true

// Начнем вызов:
funcs.run("hi");                      // Hi!
funcs.run("helloWorld");              // Hello world!
console.log(funcs.run("add", 10, 3)); // 13
console.log(funcs.run("sub", 10, 3)); // 7

// Удалим функцию helloWorld из хранилища
console.log(funcs.remove("helloWorld")); // true

// Вызов удаленной функции невозможен
console.log(funcs.run("helloWorld"));    // undefined

Данная конструкция позволяет добавлять функции, вызывать их по имени (можно даже с параметрами), а также — удалять из хранилища.


Итоги

В данном ответе я постарался рассмотреть наиболее распространенные на практике способы вызова функций (местами — не только), имея в наличии лишь строку с их названием.

Прошу обратить Ваше внимание на то, что расположены они в порядке возрастания валидности их использования. Начиная с нежелательного eval() и заканчивая реализацией своей структуры под необходимые нужды.
Вопрос, конечно, касательно рекомендаций может быть спорным, ибо на вкус и цвет, как говорится…


Если Вы считаете, что мой ответ ненароком обошел стороной какой-то очень хороший метод решения указанной проблемы — пишите в комментариях, с радостью дополню ответ!

{
    let log = (a, ...b) => document.write((b.length > 0 ? String.raw(a, ...b) : a) + '<br>n');
    let foos = {
        'добавить пользователя'() { log `пользователь добавлен`; },
        'удалить пользователя'() { log `пользователь удалён`; },
    };
    /**
     * TypeScript
     * добавить автоподстановку / проверку
     *
     * foosDO(_str: keyof typeof foos)
     */
    function foosDO(_str) {
        if (_str in foos) {
            foos[_str]();
        }
    }
    foosDO('добавить пользователя');
    foosDO('удалить пользователя');
    foosDO('ыыыыыыы');
}

введите сюда описание изображения

другой

let log = (a, ...b) => document.write((b.length > 0 ? String.raw(a, ...b) : a) + '<br>n');
void function () {
    /**
     * 'use strict'; + set(){return false}
     *  выбросит TypeError тут => user['qq'] = ''
     */
    'use strict';
    let user = { firstName: null };
    user = new Proxy(user, {
        get(target, prop) {
            log `Чтение ${prop}`;
            return target[prop];
        },
        set(target, prop, value) {
            log `Запись ${prop} ${value}`;
            if (prop in target) {
                target[prop] = value;
                return true;
            }
            else {
                log `Запись не удалась prop:${prop}`;
                return false;
            }
        }
    });
    user.firstName = "Ilya"; // запись
    log `user.firstName:${user.firstName}`; // Ilya
    user['qq'] = '';
}();

https://learn.javascript.ru/proxy
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Strict_mode

Конечный-автомат

ходи сюда https://ru.stackoverflow.com/a/845979/232932 там много написал я

Разве так сложно забить этот запрос в google/яндекс?

var a = "add_New_User";
window[a]();

Одно время была такая загвоздка, пока я не вспомнил о чудесном, но малоиспользуемом способе создания функций.

var str = 'User.create';
new Function('', 'return ' + str)()(); // Выполнится функция из строки
var func = new Function('', 'return ' + str)(); // Теперь func - ссылка на функцию из строки

// прокси позволяет отслеживать изменения любых свойств
let a = new Proxy({}, {
  set(target, prop, value) {
    let args = ''; // если нужны аргументы
    if (prop = 'name') { // задание аргументов в зависимости от записываемого свойства / прочих условий
      args = '1,2,3';
    }
    target[prop] = value; // если надо записать имя функции
    target['result'] = eval(`${value}(${args})`); // result содержит результат выполнения функции
    // target[prop] = result   для записи результата выполнения
    return true;
  },
  get(target, prop) { // если требуется получать значение
    return target[prop];
  }
});

// вариант попроще
let b = {
  FunctionName: '',
  result: '',
  set execute(val) {
    this.result = eval(`${val}()`);
    this.FunctionName = val; // если надо записать свойство
  },
  get execute() { // если требуется получать значение
    return this.FunctionName;
  }
}

function test() {
  console.log('Все работает');
}

a.execute = 'test';
b.execute = 'test';

// содержат результаты выполнения
a.result
b.result

Как вариант можно использовать токены, только что-бы создать новую функцию нужно будет добавлять ее в список со всеми остальными токенами.

function Token(tokenName, funcInit)
{
  this.tokenName = tokenName;
  this.funcInit = funcInit;
  this.call = this.funcInit;
}
function TokenList() 
{
  this.tokens = new Array();
  this.add = function(token) 
  {
    for (var i = 0; i < this.tokens.length; i++)
    {
      if (this.tokens[i].tokenName == token.tokenName)
      {
        return;
      }
    }
    this.tokens.push(token)
  };
  this.call = function (tokenName) {
    for (var i = 0; i < this.tokens.length; i++)
    {
      if (this.tokens[i].tokenName == tokenName)
      {
        this.tokens[i].call();
        return;
      }
    }
  }
}


var funcs = new TokenList();
funcs.add(new Token("Example", function() {
alert("Вызвана функция Example")
}));
funcs.add(new Token("Example2", function() {
alert("Вызвана функция Example2")
}));
funcs.add(new Token("Example3", function() {
alert("Вызвана функция Example3")
}));
funcs.add(new Token("Example3", function() {
alert("Вызвана функция Example3 Копия")
})); // не добавится


funcs.call("Example");

funcs.call("Example3");

funcs.call("Example2");

funcs.call("Example4"); //Не вызовется 

Источник

Вам также может быть интересно:

Переадресация запросов по данным из тела запроса — java nginx spring-mvc
Вопрос: Существует сервис с "кучей" контроллеров. Стоит задача "распилить" данный сервис на микросервисы. Сами микросервисы будут крутится на самостоятельных машинах. Основная проблема в том, что есть ...
Java. Деление в столбик. Съезжает вывод в консоль — java
Вопрос: Кто-нибудь может подсказать почему при делении 1034/15 съезжает вывод в консоль? public class Division { private StringBuffer result = new StringBuffer(); ...
Доступ к cookies другого сайта — javascript
Вопрос: Не знаю, или правильно написал вопрос в заголовке. Вообще вопрос такой. Есть, например виджет коминтариев фб, его можно установить на свой сайт. При заходе, он ...
Не видит загрузочной флешки с Ubuntu — linux windows ubuntu
Вопрос: Решил установить Ubuntu второй ОС. Скачал образ и установил его на флешку. Память для будущей Ubuntu выделил ещё когда устанавливал Windows. Проблема в том, что когда ...
Как устроен Netty? — java async netty
Вопрос: Немного почитал про асинхронные сокеты и про фреймворк Netty, но у меня возник вопрос о том как устроен механизм обработки многочисленных запросов к Netty. ...
Как реализовать правильную связь классов в javascript? — javascript ооп полиморфизм
Вопрос: Теперь в деталях : имеются несколько классов : class RemovedItem { constructor(value, key) { this.value = value; ...
Модификация Observable при помощи дополнительного запроса в сеть — android kotlin rxjava
Вопрос: работаю с Vk.APi и произвожу поиск списка групп. В ответе с API получаю список групп, но проблема в том, что каждая из них не содержит ...
Как загрузить Layout в Activity или View из переменной типа String — android xml activity
Вопрос: Обычно внешний вид Activity или View загружается из файлов типа *.xml, вложенных в папку res/layout проекта. А как сделать, чтобы внешний вид загружался из ...
Как выбрать максимальное значение в столбце? — sql sql-server
Вопрос: У меня есть таблица которая состоит из 2 столбцов OrderID OrderDate ------------------------------- 1021 1976-07-04 00:00:00 2312 ...
Форма TextView XML — android xml
Вопрос: Прошу понять меня правильно: Я не прошу что-то сделать за меня и предоставить готовый код ВОПРОС: Какие атрибуты необходимо использовать, чтобы выполнить такой же вид ...
Отловить закрытие консольной программы — c# console
Вопрос: Есть консольное приложение (C#), мне нужно отловить событие его закрытия. Это может быть и Ctrl+C и нажатие на крестик, вообще любое событие после которого ...
Странное поведение jQuery — jquery
Вопрос: Есть веб-страница, на которой естественно имеются стили и скрипты. При очистке кэша и полной перезагрузке страницы (Ctrl + F5) jQuery неправильно определяет height() и ...
Оверлей для любой программы и игры DirectX в фуллскрине через инжект dll — c# wpf
Вопрос: Всем привет. Хочу сделать универсальный оверлей, который при запуске из консольного приложения будет инжектить dll с самим оверлеем в любую программу, как у стима/дискорда. Про инжект ...
Использование, связка бинов в Java — java netbeans xhtml
Вопрос: У меня есть example.xhtml, я не знаю как правильно его заполнить, так как NetBeans предлагает один вариант, а видео, где все работает, заполняют по-другому.Netbeans: <?xml ...
Удалил rfremix-relese при попытке обновления — linux fedora
Вопрос: Сегодня хотел обновить Руссиан Федора Ремикс (RFRemix) 28->29. dnf upgrade выдал сообщение о конфликте в пакете rfremix-release-2.ххххххх Немного посомневался, но решил что это модуль именно ремикса ...

Оставьте ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *