Как выполнить функцию JavaScript, когда у меня есть ее имя в виде строки

1051

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

В зависимости от обстоятельств мне может понадобиться передать различные аргументы в метод тоже.

Некоторые функции могут принимать форму namespace.namespace.function(args[...]).

Кирон
источник

Ответы:

1440

Не используйте, evalесли вы абсолютно, положительно другого выбора.

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

window["functionName"](arguments);

Это, однако, не будет работать с функцией пространства имен:

window["My.Namespace.functionName"](arguments); // fail

Вот как вы это сделаете:

window["My"]["Namespace"]["functionName"](arguments); // succeeds

Чтобы сделать это проще и обеспечить некоторую гибкость, вот удобная функция:

function executeFunctionByName(functionName, context /*, args */) {
  var args = Array.prototype.slice.call(arguments, 2);
  var namespaces = functionName.split(".");
  var func = namespaces.pop();
  for(var i = 0; i < namespaces.length; i++) {
    context = context[namespaces[i]];
  }
  return context[func].apply(context, args);
}

Вы бы назвали это так:

executeFunctionByName("My.Namespace.functionName", window, arguments);

Обратите внимание, вы можете передать в любом контексте, который вы хотите, так что это будет делать то же, что и выше:

executeFunctionByName("Namespace.functionName", My, arguments);
Джейсон Бантинг
источник
4
Вы знаете, что вам не нужна вся конструкция "func"? "context.apply" один хорошо
annakata
16
Конечно, я знаю это - но способ, которым я написал функцию, обеспечивает некоторую ясность для тех, кто ее читает, которые могут не полностью понять, что происходит. Я написал эту функцию, понимая, что людям, читающим ее, может понадобиться помощь. Я предоставлю альтернативу, хотя, так как вы спросили ...
Джейсон Бантинг
108
Поцарапайте это - код достаточно ясен, и те, которые знают, знают. Если вы похожи на меня и знаете, что делаете, вы можете просто внести такие изменения самостоятельно, если использовали этот код. Переполнение стека предназначено для обучения других, и я думаю, что мой код легче понять новичку. Спасибо хоть!
Джейсон Бантинг
4
Есть ли ситуация, когда window ["funcName"] будет возвращать undefined? Это проблема, с которой я столкнулся на данный момент. Код вызова и функция определены в двух отдельных файлах js. Я пытался добавить их в тот же файл, но это не имело значения.
codemonkey
5
Я думаю, что здесь есть проблема. При звонке My.Namespace.functionName(), thisбудет ссылаться на My.Namespaceобъект. Но когда вы звоните executeFunctionByName("My.Namespace.functionName", window), нет никакого способа thisобратиться к тому же самому. Возможно, ему следует использовать последнее пространство имен в качестве области действия или windowесли нет пространств имен. Или вы можете позволить пользователю указать область действия в качестве аргумента.
JW.
100

Просто подумал, что выложу слегка измененную версию очень полезной функции Джейсона Бантинга .

Во-первых, я упростил первый оператор, указав второй параметр для slice () . Оригинальная версия работала нормально во всех браузерах, кроме IE.

Во- вторых, я заменил это с контекста в обратном заявлении; в противном случае это всегда указывало на окно, когда выполнялась целевая функция.

function executeFunctionByName(functionName, context /*, args */) {
    var args = Array.prototype.slice.call(arguments, 2);
    var namespaces = functionName.split(".");
    var func = namespaces.pop();
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    return context[func].apply(context, args);
}
Алекс Назаров
источник
Там нет проверки, чтобы увидеть, существует ли «functionName» на самом деле?
Crashalot
Я думаю, что ответ Mac недооценен. Я не эксперт, но это кажется хорошо продуманным и надежным.
Мартин Хансен Леннокс
65

Ответ на этот другой вопрос покажет вам, как это сделать: эквивалент Javascript для локальных элементов Python ()?

В принципе, вы можете сказать,

window["foo"](arg1, arg2);

или, как многие другие предложили, вы можете просто использовать eval:

eval(fname)(arg1, arg2);

хотя это крайне небезопасно, если вы не абсолютно уверены в том, что вы делаете.

Эли Кортрайт
источник
6
первая форма гораздо предпочтительнее
аннаката
19
Используйте eval только в крайнем случае, когда ничего не помогает.
Джейсон Бантинг
1
Это ... но будет ли работать с такими функциями: xyz (args)?
Kieron
@keiron: да. см. мой ответ ниже
annakata
55

Не могли бы вы просто сделать это:

var codeToExecute = "My.Namespace.functionName()";
var tmpFunc = new Function(codeToExecute);
tmpFunc();

Вы также можете выполнить любой другой JavaScript, используя этот метод.

Коли
источник
3
работает, когда с функцией
передаются
Как насчет возврата функции?
Петр Денев
12
Чем это отличается от eval("My.Namespace.functionName()");?
developerbmw
@PeterDenev просто измените первую строку наvar codeToExecute = "return My.Namespace.functionName()";
developerbmw
2
@developerbmw, вот ответ stackoverflow.com/questions/4599857/…
Tejasvi Hegde
48

Я думаю, что элегантный способ сделать это - определить ваши функции в хеш-объекте. Затем вы можете получить ссылку на эти функции из хэша, используя строку. например

var customObject = {
  customFunction: function(param){...}
};

Тогда вы можете позвонить:

customObject['customFunction'](param);

Где customFunction будет строкой, соответствующей функции, определенной в вашем объекте.

Рубен Даддарио
источник
@ibsenv, спасибо за ваш комментарий, чтобы помочь мне определить этот ответ как лучший. Я создал массив объектов функций и, в свою очередь, использовал его для создания массива deferred.promises. Я положил пример кода ниже. (Я не хотел создавать новый ответ и заимствовать ответ Рубена.)
user216661
function getMyData (arrayOfObjectsWithIds) {var functionArray = arrayOfObjectsWithIds.map (function (value) {return {myGetDataFunction: MyService.getMyData (value.id)};}) var promises = functionArray.map (function (getDataFunction) {var defer = = q.defer (); getDataFunction.myGetDataFunction.success (function (data) {deferred.resolve (data)}). error (function (error) {deferred.reject ();}); вернуть deferred.promise;}); $ q.all (обещания) .then (function (dataArray) {// do stuff})};
user216661
Это отлично работает, я только добавлю подчеркивание / lodash для проверки, если это функция. А потом беги
elporfirio
35

С ES6 вы можете получить доступ к методам класса по имени:

class X {
  method1(){
    console.log("1");
  }
  method2(){
    this['method1']();
    console.log("2");
  }
}
let x  = new X();
x['method2']();

результат будет:

1
2
Нильс Петерсон
источник
1
Лучший javascript ЧИСТЫЙ ... Бог ... удалить класс не работает, но все в порядке. Спасибо!
KingRider
1
Это то, что я искал от долгого времени. Спасибо!
PaladiN
ES2015 тут не при чем. Вы можете достичь той же цели, используя чистые объекты или делегирование прототипов через Object.create(). const myObj = {method1 () {console.log ('1')}, method2 () {console.log ('2')}} myObj ['method1'] (); // 1 myObj ['method2'] (); // 2
Сминутоли
1
Это золото !!! Я удивлен, что никогда не думал об этом раньше. Ницца!!!
Thxmike
Я также считаю, что это самый лучший способ достижения нашей цели.
Крис Юнг
24

Две вещи:

  • избегайте Eval, это очень опасно и медленно

  • во-вторых, не имеет значения, где существует ваша функция, «глобальность» не имеет значения. x.y.foo()может быть включен через x.y['foo']()или x['y']['foo']()или даже window['x']['y']['foo'](). Вы можете цепляться бесконечно, как это.

annakata
источник
1
но вы не можете использовать window ['xyz'] () для вызова xyz ()
nickf
17

Все ответы предполагают, что к функциям можно получить доступ через глобальную область (окно). Однако ФП не сделал этого предположения.

Если функции живут в локальной области видимости (или закрытии) и на них не ссылается какой-либо другой локальный объект, неудача: вам нужно использовать eval()AFAIK, увидеть динамический вызов локальной функции в javascript

Вольфганг Куен
источник
2
Чувак (или чувак), большое спасибо за указание на это! Я думал, что схожу с ума на секунду.
Funktr0n
13

Вам просто нужно преобразовать вашу строку в указатель window[<method name>]. пример:

var function_name = "string";
function_name = window[function_name];

и теперь вы можете использовать его как указатель.

Амирали
источник
Это кажется намного более безопасным способом.
Джеймс Полуз
12

Вот мой вклад в отличные ответы Джейсона Бантинга / Алекса Назарова, где я включаю проверку ошибок, запрошенную Crashalot.

С учетом этой (надуманной) преамбулы:

a = function( args ) {
    console.log( 'global func passed:' );
    for( var i = 0; i < arguments.length; i++ ) {
        console.log( '-> ' + arguments[ i ] );
    }
};
ns = {};
ns.a = function( args ) {
    console.log( 'namespace func passed:' );
    for( var i = 0; i < arguments.length; i++ ) {
        console.log( '-> ' + arguments[ i ] ); 
    }
};
name = 'nsa';
n_s_a = [ 'Snowden' ];
noSuchAgency = function(){};

тогда следующая функция:

function executeFunctionByName( functionName, context /*, args */ ) {
    var args, namespaces, func;

    if( typeof functionName === 'undefined' ) { throw 'function name not specified'; }

    if( typeof eval( functionName ) !== 'function' ) { throw functionName + ' is not a function'; }

    if( typeof context !== 'undefined' ) { 
        if( typeof context === 'object' && context instanceof Array === false ) { 
            if( typeof context[ functionName ] !== 'function' ) {
                throw context + '.' + functionName + ' is not a function';
            }
            args = Array.prototype.slice.call( arguments, 2 );

        } else {
            args = Array.prototype.slice.call( arguments, 1 );
            context = window;
        }

    } else {
        context = window;
    }

    namespaces = functionName.split( "." );
    func = namespaces.pop();

    for( var i = 0; i < namespaces.length; i++ ) {
        context = context[ namespaces[ i ] ];
    }

    return context[ func ].apply( context, args );
}

позволит вам вызывать функцию javascript по имени, хранящемуся в строке, либо в пространстве имен, либо в глобальном масштабе, с аргументами или без них (включая объекты Array), обеспечивая обратную связь по любым возникшим ошибкам (возможно, перехватывая их).

Пример вывода показывает, как это работает:

// calling a global function without parms
executeFunctionByName( 'a' );
  /* OUTPUT:
  global func passed:
  */

// calling a global function passing a number (with implicit window context)
executeFunctionByName( 'a', 123 );
  /* OUTPUT:
  global func passed:
  -> 123
  */

// calling a namespaced function without parms
executeFunctionByName( 'ns.a' );
  /* OUTPUT:
  namespace func passed:
  */

// calling a namespaced function passing a string literal
executeFunctionByName( 'ns.a', 'No Such Agency!' );
  /* OUTPUT:
  namespace func passed:
  -> No Such Agency!
  */

// calling a namespaced function, with explicit context as separate arg, passing a string literal and array 
executeFunctionByName( 'a', ns, 'No Such Agency!', [ 007, 'is the man' ] );
  /* OUTPUT:
  namespace func passed:
  -> No Such Agency!
  -> 7,is the man
  */

// calling a global function passing a string variable (with implicit window context)
executeFunctionByName( 'a', name );
  /* OUTPUT:
  global func passed:
  -> nsa
  */

// calling a non-existing function via string literal
executeFunctionByName( 'n_s_a' );
  /* OUTPUT:
  Uncaught n_s_a is not a function
  */

// calling a non-existing function by string variable
executeFunctionByName( n_s_a );
  /* OUTPUT:
  Uncaught Snowden is not a function
  */

// calling an existing function with the wrong namespace reference
executeFunctionByName( 'a', {} );
  /* OUTPUT:
  Uncaught [object Object].a is not a function
  */

// calling no function
executeFunctionByName();
  /* OUTPUT:
  Uncaught function name not specified
  */

// calling by empty string
executeFunctionByName( '' );
  /* OUTPUT:
  Uncaught  is not a function
  */

// calling an existing global function with a namespace reference
executeFunctionByName( 'noSuchAgency', ns );
  /* OUTPUT:
  Uncaught [object Object].noSuchAgency is not a function
  */
макинтош
источник
Не знаю ... это очень хорошее усилие, это ясно. Но звучит как "слишком широкий" для меня ...
TechNyquist
2
А? SO - платформа для вопросов / ответов / обучения. Я с удовольствием приведу все примеры, которые я могу придумать, чтобы, надеюсь, передать освещение. Для меня в этом все дело .
Mac
Если вы все равно используете функцию functionName, почему бы просто не использовать это?
данные
Это не работает для меня. У меня есть функция пространства имен name abcd, где d это имя функции. вызов executeFunctionByName ("abcd", окно) завершается неудачно в строке, которая проверяет, if( typeof context[ functionName ] !== 'function' )потому что context - window - определен, является объектом и массивом, но window ['abcd'] не существует, поскольку было определено как проблема в принятом ответ: window["My.Namespace.functionName"](arguments); // fail
акусматы
12

В зависимости от того, где вы находитесь, вы также можете использовать:

this["funcname"]();
self["funcname"]();
window["funcname"]();
top["funcname"]();
globalThis["funcname"]();

или в nodejs

global["funcname"]()
Zibri
источник
9

Если вы хотите вызвать функцию объекта вместо глобальной функции с помощью window["functionName"]. Вы можете сделать это как;

var myObject=new Object();
myObject["functionName"](arguments);

Пример:

var now=new Date();
now["getFullYear"]()
Ахмет Дал
источник
8

БЫТЬ ОСТОРОЖЕН!!!

Следует избегать вызова функции по строке в JavaScript по двум причинам:

Причина 1: некоторые обфускаторы кода разрушают ваш код, поскольку они изменяют имена функций, делая строку недопустимой.

Причина 2: гораздо сложнее поддерживать код, использующий эту методологию, так как гораздо труднее найти использование методов, вызываемых строкой.

dykstrad
источник
7

Вот мой подход Es6, который позволяет вам вызывать вашу функцию по имени в виде строки или имени функции, а также позволяет передавать разное количество аргументов различным типам функций:

function fnCall(fn, ...args)
{
  let func = (typeof fn =="string")?window[fn]:fn;
  if (typeof func == "function") func(...args);
  else throw new Error(`${fn} is Not a function!`);
}


function example1(arg1){console.log(arg1)}
function example2(arg1, arg2){console.log(arg1 + "  and   " + arg2)}
function example3(){console.log("No arguments!")}

fnCall("example1", "test_1");
fnCall("example2", "test_2", "test3");
fnCall(example3);
fnCall("example4"); // should raise an error in console

pouyan
источник
6

Удивлен, чтобы не упомянуть о setTimeout.

Чтобы запустить функцию без аргументов:

var functionWithoutArguments = function(){
    console.log("Executing functionWithoutArguments");
}
setTimeout("functionWithoutArguments()", 0);

Чтобы запустить функцию с аргументами:

var functionWithArguments = function(arg1, arg2) {
    console.log("Executing functionWithArguments", arg1, arg2);
}
setTimeout("functionWithArguments(10, 20)");

Для запуска глубоко именованной функции:

var _very = {
    _deeply: {
        _defined: {
            _function: function(num1, num2) {
                console.log("Execution _very _deeply _defined _function : ", num1, num2);
            }
        }
    }
}
setTimeout("_very._deeply._defined._function(40,50)", 0);
abhishekisnot
источник
Это не дает ответа на вопрос. Чтобы критиковать или запрашивать разъяснения у автора, оставьте комментарий под его постом - вы всегда можете комментировать свои собственные посты, и, когда у вас будет достаточно репутации, вы сможете комментировать любой пост .
AstroCB
Пожалуйста, добавьте пример того, как вы бы назвали runMeс несколькими аргументами.
лексикоре
1
@lexicore Я проголосовал за удаление в очереди на проверку, потому что он не дает четкого ответа на вопрос и сам по себе не имеет особой ценности.
AstroCB
1
У этого метода есть потенциально огромный недостаток, поскольку он ставит выполнение в конец очереди рендеринга , что делает этот вызов асинхронным
PeterM
1
Мне нравится этот ответ, кажется, работает для моих требований.
Квинтон
3

Итак, как и другие говорили, безусловно, лучший вариант:

window['myfunction'](arguments)

И, как сказал Джейсон Бантинг , это не сработает, если имя вашей функции содержит объект:

window['myobject.myfunction'](arguments); // won't work
window['myobject']['myfunction'](arguments); // will work

Итак, вот моя версия функции, которая будет выполнять все функции по имени (включая объект или нет):

my = {
    code : {
        is : {
            nice : function(a, b){ alert(a + "," + b); }
        }
    }
};

guy = function(){ alert('awesome'); }

function executeFunctionByName(str, args)
{
    var arr = str.split('.');
    var fn = window[ arr[0] ];
    
    for (var i = 1; i < arr.length; i++)
    { fn = fn[ arr[i] ]; }
    fn.apply(window, args);
}

executeFunctionByName('my.code.is.nice', ['arg1', 'arg2']);
executeFunctionByName('guy');

pmrotule
источник
3
  let t0 = () => { alert('red0') }
  var t1 = () =>{ alert('red1') }
  var t2 = () =>{ alert('red2') }
  var t3 = () =>{ alert('red3') }
  var t4 = () =>{ alert('red4') }
  var t5 = () =>{ alert('red5') }
  var t6 = () =>{ alert('red6') }

  function getSelection(type) {
    var evalSelection = {
      'title0': t0,
      'title1': t1,
      'title2': t2,
      'title3': t3,
      'title4': t4,
      'title5': t5,
      'title6': t6,
      'default': function() {
        return 'Default';
      }
    };
    return (evalSelection[type] || evalSelection['default'])();
  }
  getSelection('title1');

Более ООП решение ...

Лео Ланезе
источник
2

Еще одна деталь на постах Джейсона и Алекса. Я нашел полезным добавить значение по умолчанию в контекст. Просто поставьте context = context == undefined? window:context;в начале функции. Вы можете изменить windowлюбой предпочитаемый контекст, и тогда вам не нужно будет передавать одну и ту же переменную каждый раз, когда вы вызываете ее в контексте по умолчанию.

Брэдли Шрэйдер
источник
2

Чтобы добавить к ответу Джейсона Бантинга, если вы используете nodejs или что-то еще (и это работает в dom js), вы можете использовать thisвместо window(и помните: eval is evil :

this['fun'+'ctionName']();
Cilan
источник
2

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

Мое решение было очень похоже на " очень полезную функцию Джейсона Бантинга » * , хотя оно не выполнялось автоматически, и контекст всегда был в окне. Но это можно легко изменить.

Надеюсь, это будет кому-то полезно.

/**
 * Converts a string containing a function or object method name to a function pointer.
 * @param  string   func
 * @return function
 */
function getFuncFromString(func) {
    // if already a function, return
    if (typeof func === 'function') return func;

    // if string, try to find function or method of object (of "obj.func" format)
    if (typeof func === 'string') {
        if (!func.length) return null;
        var target = window;
        var func = func.split('.');
        while (func.length) {
            var ns = func.shift();
            if (typeof target[ns] === 'undefined') return null;
            target = target[ns];
        }
        if (typeof target === 'function') return target;
    }

    // return null if could not parse
    return null;
}
Nomaed
источник
1

Я не могу не упомянуть еще одну хитрость, которая помогает, если у вас есть неизвестное количество аргументов, которые также передаются как часть строки, содержащей имя функции. Например:

var annoyingstring = 'call_my_func(123, true, "blah")';

Если ваш Javascript работает на HTML-странице, все, что вам нужно, это невидимая ссылка; Вы можете передать строку в onclickатрибут и вызвать clickметод.

<a href="#" id="link_secret"><!-- invisible --></a>

$('#link_secret').attr('onclick', annoyingstring);
$('#link_secret').click();

Или создайте <a>элемент во время выполнения.

Магнус Смит
источник
Творческое решение, но оно не будет работать для аргументов типа объекта или массива.
Деннис Хайден
1
Это использует eval под капотом ... И действительно бьется вокруг куста, чтобы сделать это
Хуан Мендес
1

Самый простой способ получить к нему доступ, как элемент

window.ClientSideValidations.forms.location_form

такой же как

window.ClientSideValidations.forms['location_form']
crazycrv
источник
1

Вы можете вызвать функцию JavaScript в eval("functionname as string")любом из них. Как показано ниже: (eval - это чистая функция JavaScript)

function testfunc(){
    return "hello world";
}

$( document ).ready(function() {

     $("div").html(eval("testfunc"));
});

Рабочий пример: https://jsfiddle.net/suatatan/24ms0fna/4/

Суат Атан доктор философии
источник
Это работает хорошо, и это так просто
Карлос Э
1
И тоже очень медленно.
Марко
1

Это работает для меня:

var command = "Add";
var tempFunction = new Function("Arg1","Arg2", "window." + command + "(Arg1,Arg2)");
tempFunction(x,y);

Я надеюсь, что это работает.

DevAshish
источник
1

Я не думаю, что вам нужны сложные промежуточные функции или eval или зависеть от глобальных переменных, таких как окно:

function fun1(arg) {
  console.log(arg);
}

function fun2(arg) {
  console.log(arg);
}

const operations = {
  fun1,
  fun2
};

let temp = "fun1";

try {
  // You have to use square brackets property access
  operations["fun1"]("Hello World");
  operations["fun2"]("Hello World");
  // You can use variables
  operations[temp]("Hello World");
} catch (error) {
  console.error(error);
}

Он также будет работать с импортированными функциями:

// mode.js
export function fun1(arg) {
  console.log(arg);
}

export function fun2(arg) {
  console.log(arg);
}
// index.js
import { fun1, fun2 } from "./mod";

const operations = {
  fun1,
  fun2
};

try {
  operations["fun1"]("Hello World");
  operations["fun2"]("Hello World");
} catch (error) {
  console.error(error);
}
SnnSnn
источник
0

Без использования eval('function()')вы можете создать новую функцию с помощью new Function(strName). Приведенный ниже код был протестирован с использованием FF, Chrome, IE.

<html>
<body>
<button onclick="test()">Try it</button>
</body>
</html>
<script type="text/javascript">

  function test() {
    try {    
        var fnName = "myFunction()";
        var fn = new Function(fnName);
        fn();
      } catch (err) {
        console.log("error:"+err.message);
      }
  }

  function myFunction() {
    console.log('Executing myFunction()');
  }

</script>
Ithar
источник
0
use this

function executeFunctionByName(functionName, context /*, args */) {
      var args = [].slice.call(arguments).splice(2);
      var namespaces = functionName.split(".");
      var func = namespaces.pop();
      for(var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
      }
      return context[func].apply(context, args);
    }
Pratik
источник
1
Почему? Ответы без объяснения, скорее всего, бесполезны.
Даниэль В.
0

Смотреть основные:

var namefunction = 'jspure'; // String

function jspure(msg1 = '', msg2 = '') { 
  console.log(msg1+(msg2!=''?'/'+msg2:''));
} // multiple argument

// Results ur test
window[namefunction]('hello','hello again'); // something...
eval[namefunction] = 'hello'; // use string or something, but its eval just one argument and not exist multiple

Существуй другой тип функции - это класс и посмотри пример nils petersohn

KingRider
источник
0

Спасибо за очень полезный ответ. Я использую функцию Джейсона Бантинга в своих проектах.

Я расширил его, чтобы использовать его с дополнительным тайм-аутом, потому что обычный способ установить тайм-аут не будет работать. Смотрите вопрос abhishekisnot

function executeFunctionByName(functionName, context, timeout /*, args */ ) {
	var args = Array.prototype.slice.call(arguments, 3);
	var namespaces = functionName.split(".");
	var func = namespaces.pop();
	for (var i = 0; i < namespaces.length; i++) {
		context = context[namespaces[i]];
	}
	var timeoutID = setTimeout(
		function(){ context[func].apply(context, args)},
		timeout
	);
    return timeoutID;
}

var _very = {
    _deeply: {
        _defined: {
            _function: function(num1, num2) {
                console.log("Execution _very _deeply _defined _function : ", num1, num2);
            }
        }
    }
}

console.log('now wait')
executeFunctionByName("_very._deeply._defined._function", window, 2000, 40, 50 );

Нео
источник