Запуск функции по значению переменной — 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"); //Не вызовется 

Источник

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

Использование вложенных маршрутов в React Router — javascript reactjs react-router
Вопрос: Для организации маршрутов в приложении использую React Router. <Route path="/" component={...}> <IndexRoute component={...}/> <Route path="user/:userId" component={...}> ...
Как с помощью Retrofit 2.0 отправить данные в JSON на сервер и получить ответ? — java android retrofit
Вопрос: Только начал читать про Retrofit 2.0 до этого использовал HttpURLConnection. Как я работаю с HttpURLConnection, формирую Json перевожу его в byte, ставлю header в ...
Не приходят push уведомления. FCM — android firebase android-notification
Вопрос: Появилась необходимость реализовать push уведомления. Прописал в манифесте сервис: <service android:name=".MyFirebaseMessagingService"> <intent-filter> ...
Принцип браузерной игры в линукс терминале — java linux terminal
Вопрос: Наткнулся на Java библиотеку CHARVA. И хотел бы уточнить у знающих людей, возможно ли на основе данной библиотеки сделать программу по принципу браузерной игры, но ...
Мерцание заблокированного экрана при выключенной подсветке в Debian 8 Gnome 3 — linux debian экран
Вопрос: На ноутбуке с Debian 8 Jessie и Gnome 3 имеется следующая проблема. При выключенном заблокированном экране сквозь него можно наблюдать, как весь экран становится белым, ...
Создание WCF клиента на готовый SOAP web сервер — c# wcf
Вопрос: Доброго времени суток. Появилась задача опрашивать web сервер с клиента на котором планируется написать WCF клиентскую часть. Информации про сервер очень мало (не знаю платформу ...
Безопасно ли удалить файл логов general_log.txt? — mysql
Вопрос: При выполнении запроса со вставкой данных большого объёма SQLyog начал вылетать с ошибкой: not enough memory application terminated В связи с этим я решила ...
Callback функции создания таблицы mysql в nodejs — mysql node.js callback
Вопрос: Есть функция, которая при запуске создает базу даных, function showDb() { pool.query("show databases like 'bt' ",function (err, ...
Как создать Adapter с неограниченным количеством строк и с неограниченным разным количеством столбцов в каждой строке — java android
Вопрос: Как создать Adapter с неограниченным количеством строк и с неограниченным разным количеством столбцов в каждой строке Автор вопроса: Salut Amigo Источник
Не могу передать байтовый массив в контроллер — c# asp.net-mvc entity-framework
Вопрос: У меня изображения храняться в бд в формате байтового массива, через форич отлично все выводит, но когда я хочу открыть страницу для работы с ...
proguard release error — java android mvp
Вопрос: Включил в проекте proguard, apk собирается, все хорошо, но приложение не работает) Proguard-rules.pro -keepattributes InnerClasses -keepattributes EnclosingMethod -keepattributes *Annotation* -dontoptimize # Keep Butterknife -keep class butterknife.** { *; } -dontwarn butterknife.internal.** -keep ...
Не отрабатывает page:update — javascript ruby-on-rails
Вопрос: Есть мой учебный проект на ruby. Делаю редактирование объектов с помощью JS. Сейчас работает так: Редактирую первый раз - всё нормально. Не обновляя страницу, ...
Как найти определенный символ в строке и удалить значение после него (и вместе с ним) Jquery — javascript html jquery
Вопрос: Здравствуйте, есть определенный набор строк, типа "L / Красный / 12345", как можно на странице найти их, и вырезать из них все что находится ...
Почему не работает wildcard module declaration? — typescript
Вопрос: Почему не работает такой способ декларации: declare module "*!text" {} ? Цель - использовать контент файла в переменной: import layout = require("/js/views/layouts/wnd.html!text"); или так: import layout from "/js/views/layouts/wnd.html!text"; Если ...
Как прервать 3rd-party код? — c# многопоточность .net-core
Вопрос: Есть 3rd-party код из библиотеки который "зависает" в ожидании где-то в работе с сетью. CancellationToken поддержки нет, таймаутов нет. Запускаю я его через: Task.Run(() => ...

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

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