Як виконати функцію JavaScript, коли у мене є ім'я у вигляді рядка


1050

У мене є назва функції в JavaScript як рядок. Як перетворити це у покажчик функції, щоб я міг його зателефонувати пізніше?

Залежно від обставин, мені може знадобитися також передавати різні аргументи в метод.

Деякі функції можуть мати форму namespace.namespace.function(args[...]).

Відповіді:


1438

Не використовуйте, 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"? Один "контекст.застосувати" добре
annakata

16
Звичайно, я це знаю, - але те, як я написав функцію, забезпечує певну чіткість для тих, хто її читає, що може не повністю збагнути те, що відбувається. Я написав цю функцію, розуміючи, що люди, які її читають, може потребувати допомоги. Я надам альтернативний варіант, оскільки ви попросили ...
Джейсон Бантінг

108
Почіпте це - код досить зрозумілий і ті, хто знає, знає. Якщо ви схожі на мене, і знаєте, що ви робите, ви можете просто внести такі зміни самостійно, якщо використовували цей код. Переповнення стека - це освіта інших, і я вважаю, що мій код простіше зрозуміти новачкові. Дякую, хоча!
Джейсон Бантінг

4
Чи існує ситуація, коли вікно ["funcName"] повернеться невизначеним? Це проблема, яку я маю на даний момент. Код виклику та функція визначаються у двох окремих 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

Я думаю, що відповідь Мака недооцінена. Я не експерт, але це здається продуманим і надійним.
Мартін Хансен Леннокс

65

Відповідь на це інше питання показує вам, як це зробити: Javascript еквівалент місцевих жителів Python ()?

В основному, можна сказати

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

або як багато інших запропонували, ви можете просто використовувати eval:

eval(fname)(arg1, arg2);

Хоча це вкрай небезпечно, якщо ви абсолютно не впевнені в тому, що ви робите.


6
перша форма є набагато кращою
annakata

19
Використовуйте eval в крайньому випадку, коли все інше не вдасться.
Джейсон Бантінг

1
Це ... але чи буде він працювати з такими функціями: xyz (args)?
Кієрон

@keiron: так. дивіться мою відповідь нижче
annakata

55

Не могли б ви це зробити:

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

Ви також можете виконати будь-який інший JavaScript, використовуючи цей метод.


3
працює, коли навіть аргументи передаються з функцією
adeel41,

Що щодо повернення функції?
Петро Денев

12
Чим це відрізняється від eval("My.Namespace.functionName()");?
developerbmw

@PeterDenev просто змінив перший рядок наvar codeToExecute = "return My.Namespace.functionName()";
developerbmw

2
@developerbmw, ось відповідь stackoverflow.com/questions/4599857 / ...
Tejasvi Хедж

48

Я думаю, що елегантний спосіб зробити це, визначивши свої функції в хеш-об'єкті. Тоді ви можете мати посилання на ці функції з хеша, використовуючи рядок. напр

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

Тоді ви можете зателефонувати:

customObject['customFunction'](param);

Де customFunction - це рядок, що відповідає функції, визначеній у вашому об'єкті.


@ibsenv, дякую за ваш коментар, щоб допомогти мені визначити цю відповідь найкращою. Я створив масив функціональних об'єктів і, в свою чергу, використовував його для створення масиву deferred.promises. Я ставлю приклад коду нижче. (Я не хотів створювати нову відповідь і запозичувати відповідь Рубена.)
користувач216661

функція getMyData (arrayOfObjectsWithIds) {var functionArray = arrayOfObjectsWithIds.map (функція (значення) {return {myGetDataFunction: MyService.getMyData (value.id)};}) var promises = functionArray.map (function (getData) {функція (getData) varer = deferta (varDa); q.defer (); getDataFunction.myGetDataFunction.success (функція (дані) {deferred.resolve (дані)}) помилка (функція (помилка) {deferred.reject ();}); повернути deferred.promise;}); $ q.all (обіцянки) .then (функція (dataArray) {// do stuff})};
користувач216661

Це працює чудово, я додаю лише підкреслення / 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
Це те, що я шукав давно. Дякую!
ПаладіН

ES2015 тут ні до чого. Ви можете досягти тієї ж мети, використовуючи чисті об'єкти або делегуючи прототип через Object.create(). const myObj = {method1 () {console.log ('1')}, method2 () {console.log ('2')}} myObj ['method1'] (); // 1 myObj ['method2'] (); // 2
sminutoli

1
Це золото !!! Я здивований, що ніколи раніше про це не думав. Приємно !!!
thxmike

Я також думаю, що це найновіший спосіб досягти нашої мети.
Кріс Юнг

24

Дві речі:

  • уникайте eval, це страшенно небезпечно і повільно

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


1
але ви не можете зробити вікно ['xyz'] (), щоб зателефонувати xyz ()
nickf

17

Усі відповіді передбачають, що до функцій можна отримати доступ через глобальну область (вікно). Однак ОП не зробила цього припущення.

Якщо функції живуть у локальному масштабі (він же закривається) і не посилаються на якийсь інший локальний об'єкт, невдача: Вам потрібно використовувати eval()AFAIK, дивіться динамічно викликати локальну функцію в javascript


2
Чувак (або дудет), дуже дякую, що вказали на це! Я думав, що на секунду я з’їду з розуму.
Funktr0n

13

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

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

і тепер ви можете використовувати його як вказівник.


Це здається набагато безпечнішим способом.
Джеймс Пулоз

12

Ось мій внесок у відмінні відповіді Джейсона Бантінга / Алекса Назарова, де я включаю перевірку помилок, яку вимагає Крашалот.

Враховуючи цю (надуману) преамбулу:

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 за ім'ям, збереженим у рядку, або в просторі імен, або у глобальному масштабі, з аргументами або без них (включаючи об’єкти масиву), надаючи відгуки про будь-які виниклі помилки (сподіваємось, їх виявити).

Вибірка вибірки показує, як це працює:

// 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
Так? ТАК - це питання / відповідь / навчальна платформа. Я з радістю надам усі приклади, про які я можу придумати, щоб сподіватися на ілюмінацію. Для мене в цьому справа .
Мак

Якщо ви все-таки оцінюєте функціюName, чому б не просто використати її?
дані

Це не працює для мене. У мене є функція abcd в просторі, де d - ім'я функції. виклик ExecuteFunctionByName ("abcd", вікно) не працює на лінії, яка перевіряєif( typeof context[ functionName ] !== 'function' ) тому що контекст - вікно - визначено, є об'єктом і масивом, але вікно ['abcd'] не існує, як було визначено як проблема у прийнятому відповідь: window["My.Namespace.functionName"](arguments); // fail
akousmata

12

Залежно від місця, де ви також можете використовувати:

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

або, у nodejs

global["funcname"]()

9

Якщо ви хочете викликати функцію об'єкта замість глобальної функції з window["functionName"]. Ви можете робити так, як;

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

Приклад:

var now=new Date();
now["getFullYear"]()

8

БУДЬ ОБЕРЕЖНИЙ!!!

Слід уникати виклику функції по рядку в JavaScript з двох причин:

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

Причина 2: Набагато складніше підтримувати код, який використовує цю методологію, оскільки набагато складніше знаходити звичаї методів, викликаних рядком.


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


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

Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх публікацією - ви завжди можете коментувати свої власні публікації, і як тільки у вас буде достатня репутація, ви зможете коментувати будь-яку публікацію .
AstroCB

Будь ласка, додайте приклад того, як ви зателефонували runMeза допомогою кількох аргументів.
lexicore

1
@lexicore Я проголосував за видалення в черзі на огляд, оскільки це чітко не дає суттєвої відповіді на питання і мало по собі має значення.
AstroCB

1
Цей метод має потенційно величезний недолік, оскільки він ставить виконання до кінця візуалізації черги, завдяки чому цей виклик
стає

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


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

Більше рішення OOP ...


2

Ще одна деталь щодо публікацій Джейсона та Алекса. Мені було корисно додати значення контексту за замовчуванням. Просто поставте context = context == undefined? window:context;на початку функції. Ви можете змінити windowбудь-який бажаний контекст, і тоді вам не потрібно буде передавати одну і ту ж змінну щоразу, коли ви називаєте це у контексті за замовчуванням.


2

Щоб додати відповідь Джейсона Бантінга, якщо ви використовуєте nodejs або щось подібне (і це працює і в dom js), ви можете використовувати thisзамість window(і пам’ятайте: eval is evil :

this['fun'+'ctionName']();

2

У моєму коді є дуже схожа річ. У мене створена серверна рядок, яка містить ім'я функції, яке мені потрібно передати як зворотний виклик для сторонньої бібліотеки. Тож у мене є код, який приймає рядок і повертає "покажчик" на функцію, або null, якщо він не знайдений.

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

Сподіваємось, це комусь буде корисно.

/**
 * 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;
}


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']

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/


Це чудово працює і його так просто
Carlos E

1
А також дуже повільно.
Марко

1

Це працює для мене:

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

Сподіваюся, це спрацює.


1

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

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

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>

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

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

Існує функція іншого типу класу, а приклад виглядає нульовим петерсоном


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.