Имя динамической функции в javascript?


87

У меня есть это:

this.f = function instance(){};

Я бы хотел вот это:

this.f = function ["instance:" + a](){};

10
Вы не можете. Но можноthis["instance"] = function() { }
Райнос


Чтобы прояснить, о чем говорил Рейнос, вы можете сделать this["instance" + a] = function() { }. Мне это было непонятно.
Эндрю

Ответы:


8

Обновить

Как уже упоминалось, это не самое быстрое и не самое рекомендуемое решение. Решение Маркоса, приведенное ниже, - это правильный путь.

Вы можете использовать eval:

var code = "this.f = function " + instance + "() {...}";
eval(code);

1
вот что я просил, спасибо! (в любом случае я не буду использовать эту функцию, так как она слишком медленная)
Totty.js

3
Я знаю, что это то, о чем просил ОП, но это ужасная идея. То, что вы можете, не означает, что вы должны делать что-то подобное. Есть гораздо лучшие альтернативы, которые практически идентичны по функциональности.
Томас Эдинг

4
@ sg3s: вы можете предложить другое решение?
Tom

2
@ sg3s: Спасибо, что ответили на мой комментарий! Позвольте мне объяснить, что я имел в виду и что на самом деле хочу спросить: действительно ли решение Marcosc существенно отличается от eval? Действительно ли имеет значение, если вы что-то оцениваете или передаете это конструктору функции? Если да, то можете ли вы объяснить разницу и ее последствия? Благодарность!
Tom

5
@Tom Для будущих читателей: это решение на самом деле не отличается от ответа Маркоса, они оба используют eval()( Functionконструктор делает это внутри).
капа

126

Это в основном сделает это на самом простом уровне:

"use strict";
var name = "foo";
var func = new Function(
     "return function " + name + "(){ alert('sweet!')}"
)();

//call it, to test it
func();

Если вы хотите получить больше фантазии, я написал статью « Динамические имена функций в JavaScript ».


Хорошая работа! Я только что обнаружил это самостоятельно и собирался опубликовать по одному из этих вопросов, но вы меня опередили. В последнее время я много работал с backbone.js и устал видеть «ребенка» повсюду в моем отладчике Chrome. Это решает эту проблему. Интересно, есть ли какие-либо последствия для производительности, такие как использование eval.
webXL

Есть также последствия для безопасности. Я получаю «Конструктор функции - это eval» из jsHint, поэтому я включаю это в проверку режима отладки, поскольку это единственная причина использовать это. Я предполагаю, что "use strict" предотвратит любое искажение глобального объекта, но любые аргументы конструктора функции могут быть изменены, и все, что установлено для 'this'.
webXL

Да, это для экстремальных ситуаций, когда нужно что-то построить на лету.
Marcosc

1
Я честно думаю, что stackoverflow.com/a/40918734/2911851 - лучшее решение, нет необходимости включать тело функции в виде строки (если я чего-то не упускаю, но я просто пробовал и отлично работал).
Джан Франко Забарино

2
AirBnB настоятельно не рекомендует этого делать , поскольку конструктор Function будет использовать его evalдля оценки javascript, открывая тем самым ваш код для множества уязвимостей.
Филипп Эбер

42

Вы можете использовать Object.defineProperty, как указано в MDN JavaScript Reference [1]:

var myName = "myName";
var f = function () { return true; };
Object.defineProperty(f, 'name', {value: myName, writable: false});
  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#Description

2
Оказывается , это устанавливает свойство имени , как задумано, но если я вхожу функцию в браузере (Firefox последнего DEV издание) он печатает function fn(), fnбудучи оригинальным названием. Странно.
Киара Гроустра

тот же комментарий к google chrome evergreen. Свойство установлено правильно, но имя в консоли - это исходное имя функции. Ср. 2ality.com/2015/09/… Такое впечатление, что имя функции назначается при создании и не может быть изменено?
user3743222 01

На этой странице 2ality.com см. Следующий параграф «Изменение имени функций» [1], в котором описывается тот же метод, что и в справочнике MDN Javascript. 1. 2ality.com/2015/09/…
Даррен

35

В последних двигателях вы можете делать

function nameFunction(name, body) {
  return {[name](...args) {return body(...args)}}[name]
}



const x = nameFunction("wonderful function", (p) => p*2)
console.log(x(9)) // => 18
console.log(x.name) // => "wonderful function"


1
Потратив часы на поиск решения, я даже не подумал использовать объект с вычисленным именем свойства. Именно то, что мне нужно. Благодарность!
Леви Робертс

7
Хотя это действительно работает, я начал использовать Object.defineProperty(func, 'name', {value: name})в своем собственном коде, так как думаю, что он может быть немного более естественным и понятным.
kybernetikos

он работает с транспилированным кодом? (Вавилонский или машинописный текст)
Hitmands

3
Отвечая на свой вопрос: {[expr]: val}инициализатор объекта (как объект JSON), где exprкакое-то выражение; все, что он оценивает, является ключевым. {myFn (..){..} }это сокращение для {myFn: function myFn(..){..} }. Обратите внимание, что function myFn(..) {..}может использоваться как выражение, как анонимная функция, только myFnбудет иметь имя. Последнее [name]- это просто доступ к члену объекта (как obj.keyили obj['key']). ...- оператор распространения. (Основной источник: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Джейсон Янг,

1
Спасибо за решение! Примечание. Это не сохраняет значение this. Например obj={x:7,getX(){return this.x}}; obj.getX=nameFunction('name',obj.getX); obj.getX();не сработает. Вы можете отредактировать свой ответ и использовать function nameFunction(name, body) { return {[name](...args) {return body.apply(this, args)}}[name] }вместо него!
TS

11

Я думаю, что большинство предложений здесь неоптимальны с использованием eval, хакерских решений или оберток. Начиная с ES2015 имена выводятся из синтаксической позиции для переменных и свойств.

Так что это будет работать нормально:

const name = 'myFn';
const fn = {[name]: function() {}}[name];
fn.name // 'myFn'

Не поддавайтесь искушению создать методы фабрики именованных функций, поскольку вы не сможете передать функцию извне и модифицировать ее в синтаксической позиции, чтобы вывести ее имя. Тогда уже поздно. Если вам это действительно нужно, вам нужно создать оболочку. Кто-то сделал это здесь, но это решение не работает для классов (которые также являются функциями).

Здесь написан гораздо более подробный ответ со всеми описанными вариантами: https://stackoverflow.com/a/9479081/633921


1
Как этот ответ улучшает stackoverflow.com/a/41854075/3966682 ?
d4nyll 05

1
@ d4nyll Я уже ответил на это: он создает оболочку, которая нарушает функции с сохранением состояния (функции, использующие "this"), то есть классы.
Albin

@Albin FWIW, мне совсем не понятно, как это сказано в вашем ответе. В любом случае, это хорошо знать.
Джейсон Янг

@JasonYoung "Не поддавайтесь искушению создать методы фабрики именованных функций, поскольку вы не сможете передать функцию извне и модифицировать ее в синтаксическую позицию, чтобы вывести ее имя. Тогда уже слишком поздно. Если вам это действительно нужно, вы нужно создать оболочку. Кто-то сделал это здесь, но это решение не работает для классов (которые также являются функциями) ".
Альбин

3

Что о

this.f = window["instance:" + a] = function(){};

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


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

1
Что ж, тогда вы могли бы сказать это в своем вопросе.
entonio

3

Синтаксис function[i](){}подразумевает объект со значениями свойств , которые являются функциями, function[]индексированных по названию, [i].
Итак
{"f:1":function(){}, "f:2":function(){}, "f:A":function(){}, ... } ["f:"+i].

{"f:1":function f1(){}, "f:2":function f2(){}, "f:A":function fA(){}} ["f:"+i]сохранит идентификацию имени функции. См. Примечания ниже относительно: .

Так,

javascript: alert(
  new function(a){
    this.f={"instance:1":function(){}, "instance:A":function(){}} ["instance:"+a]
  }("A") . toSource()
);

отображается ({f:(function () {})})в FireFox.
(Это почти та же идея, что и в этом решении , только оно использует общий объект и больше не заполняет объект окна функциями напрямую.)

Этот метод явно заполняет среду instance:x.

javascript: alert(
  new function(a){
    this.f=eval("instance:"+a+"="+function(){})
  }("A") . toSource()
);
alert(eval("instance:A"));

отображает

({f:(function () {})})

а также

function () {
}

Хотя функция свойства fссылается на, anonymous functionа не на instance:x, этот метод позволяет избежать некоторых проблем с этим решением .

javascript: alert(
  new function(a){
    eval("this.f=function instance"+a+"(){}")
  }("A") . toSource()
);
alert(instanceA);    /* is undefined outside the object context */

отображает только

({f:(function instanceA() {})})
  • Встроенный :делает javascript function instance:a(){}недействительным.
  • Вместо ссылки фактическое текстовое определение функции анализируется и интерпретируется eval.

Следующее не обязательно проблематично,

  • instanceAФункция непосредственно не доступна для использования в качествеinstanceA()

и поэтому гораздо больше соответствует исходному контексту проблемы.

Учитывая эти соображения,

this.f = {"instance:1": function instance1(){},
          "instance:2": function instance2(){},
          "instance:A": function instanceA(){},
          "instance:Z": function instanceZ(){}
         } [ "instance:" + a ]

максимально поддерживает глобальную вычислительную среду с семантикой и синтаксисом примера OP.


Это разрешение работает только для статических имен или с именами динамических свойств ES6. Например, (name => ({[name]:function(){}})[name])('test')работает, но (name => {var x={}; x[name] = function(){}; return x[name];})('test')не работает
William Leung

3

Ответ, получивший наибольшее количество голосов, уже имеет тело функции [String]. Я искал решение переименовать уже объявленное имя функции и, наконец, после часа борьбы я с этим справился. Это:

  • принимает объявленную alredy функцию
  • анализирует его на [String] с помощью .toString() метода
  • затем перезаписывает имя (названной функции) или добавляет новое (если анонимно) между functionи(
  • затем создает новую переименованную функцию с new Function()конструктором

function nameAppender(name,fun){
  const reg = /^(function)(?:\s*|\s+([A-Za-z0-9_$]+)\s*)(\()/;
  return (new Function(`return ${fun.toString().replace(reg,`$1 ${name}$3`)}`))();
}

//WORK FOR ALREADY NAMED FUNCTIONS:
function hello(name){
  console.log('hello ' + name);
}

//rename the 'hello' function
var greeting = nameAppender('Greeting', hello); 

console.log(greeting); //function Greeting(name){...}


//WORK FOR ANONYMOUS FUNCTIONS:
//give the name for the anonymous function
var count = nameAppender('Count',function(x,y){ 
  this.x = x;
  this.y = y;
  this.area = x*y;
}); 

console.log(count); //function Count(x,y){...}


2

Динамические методы объекта могут быть созданы с использованием расширений литералов объекта, предоставляемых ECMAScript 2015 (ES6):

const postfixes = ['foo', 'bar'];

const mainObj = {};

const makeDynamic = (postfix) => {
  const newMethodName = 'instance: ' + postfix;
  const tempObj = {
    [newMethodName]() {
      console.log(`called method ${newMethodName}`);
    }
  }
  Object.assign(mainObj, tempObj);
  return mainObj[newMethodName]();
}

const processPostfixes = (postfixes) => { 
  for (const postfix of postfixes) {
    makeDynamic(postfix); 
  }
};

processPostfixes(postfixes);

console.log(mainObj);

Результат выполнения приведенного выше кода:

"called method instance: foo"
"called method instance: bar"
Object {
  "instance: bar": [Function anonymous],
  "instance: foo": [Function anonymous]
}

это больше похоже на: o={}; o[name]=(()=>{})а не наfunction <<name>>(){}
Санкарн

2

Для установки имени существующей анонимной функции:
(на основе ответа @Marcosc)

var anonymous = function() { return true; }

var name = 'someName';
var strFn = anonymous.toString().replace('function ', 'return function ' + name);
var fn = new Function(strFn)();

console.log(fn()); // —> true

Демо .

Примечание : не делайте этого; /


Когда вы голосуете против, это нормально. Но вы должны быть вежливы в своих рассуждениях, чтобы мы могли поучиться у вас. OP запрашивает имена "динамических" функций. Это один из способов сделать это, но я никогда не рекомендовал и никогда не делал этого.
Онур Йылдырым

1

Есть два способа добиться этого, и у них есть свои плюсы и минусы.


name определение свойства

Определение неизменяемого name свойства функции.

Плюсы

  • Для имени доступен каждый символ. (например () 全 {}/1/얏호/ :D #GO(@*#%! /*)

Минусы

  • Функция в синтаксической ( «экспрессивный») имя может не совпадать с его nameзначением свойства.

Оценка выражения функции

Создание именованного выражения функции и его оценка с помощьюFunction конструктора.

Плюсы

  • Функция в синтаксической ( «экспрессивный») имя всегда соответствует его nameзначению свойства.

Минусы

  • Для имени нельзя использовать пробелы (и т. Д.).
  • Вводимое выражением (например, для ввода (){}/1//выражение return function (){}/1//() {}дает NaNвместо функции.).

const demoeval = expr => (new Function(`return ${expr}`))();

// `name` property definition
const method1 = func_name => {
    const anon_func = function() {};
    Object.defineProperty(anon_func, "name", {value: func_name, writable: false});
    return anon_func;
};

const test11 = method1("DEF_PROP"); // No whitespace
console.log("DEF_PROP?", test11.name); // "DEF_PROP"
console.log("DEF_PROP?", demoeval(test11.toString()).name); // ""

const test12 = method1("DEF PROP"); // Whitespace
console.log("DEF PROP?", test12.name); // "DEF PROP"
console.log("DEF PROP?", demoeval(test12.toString()).name); // ""

// Function expression evaluation
const method2 = func_name => demoeval(`function ${func_name}() {}`);

const test21 = method2("EVAL_EXPR"); // No whitespace
console.log("EVAL_EXPR?", test21.name); // "EVAL_EXPR"
console.log("EVAL_EXPR?", demoeval(test21.toString()).name); // "EVAL_EXPR"

const test22 = method2("EVAL EXPR"); // Uncaught SyntaxError: Unexpected identifier

1

Если вы хотите иметь динамическую функцию, такую ​​как __callфункция в PHP, вы можете использовать прокси.

const target = {};

const handler = {
  get: function (target, name) {
    return (myArg) => {
      return new Promise(resolve => setTimeout(() => resolve('some' + myArg), 600))
    }
  }
};

const proxy = new Proxy(target, handler);

(async function() {
  const result = await proxy.foo('string')
  console.log('result', result) // 'result somestring' after 600 ms
})()

1

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

1) Определите функцию Separate и вызовите ее

let functionName = "testFunction";
let param = {"param1":1 , "param2":2};

var func = new Function(
   "return " + functionName 
)();

func(param);

function testFunction(params){
   alert(params.param1);
}

2) Определите динамический код функции

let functionName = "testFunction(params)";
let param = {"param1":"1" , "param2":"2"};
let functionBody = "{ alert(params.param1)}";

var func = new Function(
    "return function " + functionName + functionBody 
)();

func(param);

0

Спасибо, Маркоск! Основываясь на его ответе, если вы хотите переименовать какую-либо функцию, используйте это:

// returns the function named with the passed name
function namedFunction(name, fn) {
    return new Function('fn',
        "return function " + name + "(){ return fn.apply(this,arguments)}"
    )(fn)
}

0

Эта служебная функция объединяет несколько функций в одну (с использованием настраиваемого имени), единственное требование состоит в том, чтобы предоставленные функции были правильно «выровнены» в начале и в конце своего цикла.

const createFn = function(name, functions, strict=false) {

    var cr = `\n`, a = [ 'return function ' + name + '(p) {' ];

    for(var i=0, j=functions.length; i<j; i++) {
        var str = functions[i].toString();
        var s = str.indexOf(cr) + 1;
        a.push(str.substr(s, str.lastIndexOf(cr) - s));
    }
    if(strict == true) {
        a.unshift('\"use strict\";' + cr)
    }
    return new Function(a.join(cr) + cr + '}')();
}

// test
var a = function(p) {
    console.log("this is from a");
}
var b = function(p) {
    console.log("this is from b");
}
var c = function(p) {
    console.log("p == " + p);
}

var abc = createFn('aGreatName', [a,b,c])

console.log(abc) // output: function aGreatName()

abc(123)

// output
this is from a
this is from b
p == 123

0

У меня была лучшая удача в сочетании ответ Даррена и ответ kyernetikos игровая .

const nameFunction = function (fn, name) {
  return Object.defineProperty(fn, 'name', {value: name, configurable: true});
};

/* __________________________________________________________________________ */

let myFunc = function oldName () {};

console.log(myFunc.name); // oldName

myFunc = nameFunction(myFunc, 'newName');

console.log(myFunc.name); // newName

Примечание: configurableустановлено в trueсоответствии со стандартной спецификацией ES2015 для Function.name 1

Это особенно помогло обойти ошибку в Webpack, подобную этой. .

Обновление: я думал опубликовать это как пакет npm, но этот пакет от sindresorhus делает то же самое.

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name

0

Я много боролся с этой проблемой. Решение @Albin работало как шарм при разработке, но оно не сработало, когда я изменил его на производство. После некоторой отладки я понял, как добиться того, что мне нужно. Я использую ES6 с CRA (create-react-app), что означает, что он связан с Webpack.

Допустим, у вас есть файл, в котором экспортируются нужные вам функции:

myFunctions.js

export function setItem(params) {
  // ...
}

export function setUser(params) {
  // ...
}

export function setPost(params) {
  // ...
}

export function setReply(params) {
  // ...
}

И вам нужно динамически вызывать эти функции в другом месте:

myApiCalls.js

import * as myFunctions from 'path_to/myFunctions';
/* note that myFunctions is imported as an array,
 * which means its elements can be easily accessed
 * using an index. You can console.log(myFunctions).
 */

function accessMyFunctions(res) {
  // lets say it receives an API response
  if (res.status === 200 && res.data) {
    const { data } = res;
    // I want to read all properties in data object and 
    // call a function based on properties names.
    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        // you can skip some properties that are usually embedded in
        // a normal response
        if (key !== 'success' && key !== 'msg') {
          // I'm using a function to capitalize the key, which is
          // used to dynamically create the function's name I need.
          // Note that it does not create the function, it's just a
          // way to access the desired index on myFunctions array.
          const name = `set${capitalizeFirstLetter(key)}`;
          // surround it with try/catch, otherwise all unexpected properties in
          // data object will break your code.
          try {
            // finally, use it.
            myFunctions[name](data[key]);
          } catch (error) {
            console.log(name, 'does not exist');
            console.log(error);
          }
        }
      }
    }
  }
}


0

лучший способ - создать объект со списком динамических функций, например:

const USER = 'user';

const userModule = {
  [USER + 'Action'] : function () { ... }, 
  [USER + 'OnClickHandler'] : function () { ... }, 
  [USER + 'OnCreateHook'] : function () { ... }, 
}


-2

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

var namedFunction = function namedFunction (a,b) {return a+b};

alert(namedFunction(1,2));
alert(namedFunction.name);
alert(namedFunction.toString());

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

(function(renamedFunction) {

  alert(renamedFunction(1,2));
  alert(renamedFunction.name);
  alert(renamedFunction.toString());
  alert(renamedFunction.apply(this,[1,2]));


})(function renamedFunction(){return namedFunction.apply(this,arguments);});

function namedFunction(a,b){return a+b};


Функция / метод nameполезны, поскольку теперь выводятся из переменных и свойств. Он также используется в трассировке стека. Ex var fn = function(){}; console.log(fn.name). Он неизменен, поэтому вы не можете изменить его позже. Если вы напишете фабричный метод, который называет все функции, fnэто усложнит отладку.
Albin


-9

Это ЛУЧШЕЕ решение, тогда лучше new Function('return function name(){}')().

Eval - самое быстрое решение:

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

var name = 'FuncName'
var func = eval("(function " + name + "(){})")

Всем, кто это читает, следуйте, следуйте, следуйте этому ответу.
Maxmaxmaximus 01

1
@majidar, если парень прав, он самый быстрый: jsperf.com/dynamicfunctionnames/1 . Однако это далеко не самый безопасный вариант.
Sancarn
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.