Назва динамічної функції в javascript?


87

У мене є таке:

this.f = function instance(){};

Я хотів би мати таке:

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

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


Щоб пояснити, що говорив Рейнос, ви можете це зробити this["instance" + a] = function() { }. Мені це не було зрозуміло.
Ендрю

Відповіді:


7

оновлення

Як зазначали інші, це не найшвидше і не найбільш рекомендоване рішення. Розчин Маркоска нижче - це шлях.

Ви можете використовувати eval:

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

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

3
Я знаю, що про це просив ОП, але це жахлива ідея. Те, що ти можеш, не означає, що ти повинен робити щось подібне. Є набагато кращі альтернативи, які майже точно такі ж функціональні.
Томас Едінг,

4
@ sg3s: чи можете ви запропонувати інше рішення?
Том

2
@ sg3s: Дякую, що відповіли на мій коментар! Дозвольте пояснити, що я мав на увазі, і що я насправді хочу запитати: чи дійсно рішення Маркоска суттєво відрізняється від eval? Чи справді має значення, якщо ви щось оцінюєте або подаєте в конструктор функцій? Якщо так, чи можете ви пояснити різницю та її наслідки? Дякую!
Том

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, і мені набридло бачити "дитину" скрізь у своєму хромовому налагоджувачі. Це вирішує цю проблему. Цікаво, чи є якісь наслідки для продуктивності, такі як використання eval.
webXL

Є також наслідки для безпеки. Я отримую "Конструктор функцій eval" від jsHint, тому я обертаю це у перевірку режиму налагодження, оскільки це єдина причина використовувати це. Я гадаю, що "використання строгого" запобіжить будь-яке знущання з глобальним об'єктом, але будь-які аргументи конструктора функції можуть бути змінені, і для будь-якого значення "це" встановлено значення.
webXL

Так, це для екстремальних ситуацій, коли вам потрібно щось побудувати на льоту.
Маркоск

1
Я чесно думаю, що stackoverflow.com/a/40918734/2911851 є кращим рішенням, не потрібно включати тіло функції як рядок (якщо я чогось не пропустив, але я просто спробував і працював чудово).
Gian Franco Zabarino

2
AirBnB настійно не рекомендує цього , оскільки конструктор функцій буде використовувати evalдля оцінки javascript - таким чином відкриваючи ваш код до безлічі вразливостей.
Філіпп Геберт,

42

Ви можете використовувати Object.defineProperty, як зазначено у Довідковому документі MDN JavaScript [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
Здається, це встановлює властивість name за призначенням, однак, якщо я реєструю функцію у своєму браузері (останнє видання Firefox dev), вона друкується function fn(), fnбудучи оригінальною назвою. Дивно.
Kiara Grouwstra

той же коментар до google chrome evergreen. Властивість встановлено належним чином, але ім'я в консолі - це оригінальне ім'я функції. Пор. 2ality.com/2015/09/… Здається, що назва функції присвоюється при створенні і не може бути змінена?
user3743222

На цій сторінці 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

це працює з переписаним кодом? (
Виводи

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, hacky або обгортки. Станом на 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

1
@ d4nyll Я вже відповів, що: Він створює обгортку, яка порушує функції стану (функції, що використовують "this") aka-класи.
Альбін

@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посилається на a, 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() {})})
  • Вбудований : робить javascriptfunction 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 запитує "динамічні" імена функцій. Це один із способів зробити це, але я ніколи не рекомендував би і не робив цього.
Onur Yıldırım

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-response-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

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

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є корисним, оскільки зараз випливає із змінної та властивостей. Він також використовується в слідах стека. Напр var fn = function(){}; console.log(fn.name). Він незмінний, тому ви не можете змінити його пізніше. Якщо ви пишете заводський метод, який називає всі функції, fnто це ускладнить налагодження.
Альбін,


-9

Це КРАЩЕ рішення, краще тоді new Function('return function name(){}')().

Eval - найшвидше рішення:

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

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

Той, хто читає це, слідкуйте за наступним, слідуйте цій відповіді.
Maxmaxmaximus

1
@majidari, якщо хлопець правильний, це найшвидше: jsperf.com/dynamicfunctionnames/1 . Це далеко не найбезпечніше.
Sancarn
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.