Страницы

Поиск по вопросам

четверг, 19 декабря 2019 г.

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

#javascript #функции


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

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


Ответы

Ответ 1



Итак, этим ответом я постараюсь наконец исчерпать данную тему, которая постоянно всплывает на (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. Стоит отметить следующие важные отличия от прошлого подхода: В отличие от eval(), при помощи new Function() мы не просто исполняем код из строки, а мы создаем из него полноценную функцию, аналогичную по свойствам тем, что мы определилили через function() { } (так как глобальный объект Function наследует некоторые методы и свойства через цепочку прототипов объекта Function.prototype), которая может принимать входные параметры, а также может быть переиспользована В отличие от eval(), функции, созданные с помощью new Function(), не могут ссылаться на область видимости, в которой они были созданы, так как при вызове они имеют доступ только к глобальным и своим внутренним переменным! В отличие от 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() и заканчивая реализацией своей структуры под необходимые нужды. Вопрос, конечно, касательно рекомендаций может быть спорным, ибо на вкус и цвет, как говорится... Если Вы считаете, что мой ответ ненароком обошел стороной какой-то очень хороший метод решения указанной проблемы - пишите в комментариях, с радостью дополню ответ!

Ответ 2



Разве так сложно забить этот запрос в google/яндекс? var a = "add_New_User"; window[a]();

Ответ 3



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

Ответ 4



// прокси позволяет отслеживать изменения любых свойств 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

Ответ 5



{ let log = (a, ...b) => document.write((b.length > 0 ? String.raw(a, ...b) : a) + '
\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) + '
\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 там много написал я

Ответ 6



Как вариант можно использовать токены, только что-бы создать новую функцию нужно будет добавлять ее в список со всеми остальными токенами. 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"); //Не вызовется

Комментариев нет:

Отправить комментарий