Есть ли какой-нибудь нереальный способ создать функцию с именем, определенным во время выполнения?

Есть ли способ создать функцию с реальным именем, которое определяется во время выполнения без использования evalи использовать только чистый JavaScript? (Таким образом, не генерируется script элементы, так как они являются специфическими для среды браузера [и во многих eval в любом случае замаскированный]; не использовать нестандартные функции одного конкретного движка JavaScript и т. д.)

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

// NOT this
var name = /* ...come up with the name... */;
var obj = {};
obj[name] = function() { /* ... */ };

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

3 ответа

Решение

Ответ для ECMAScript 2015 (он же "ES6"):

Да Начиная с ES2015, функция, созданная выражением анонимной функции, назначенным свойству объекта, получает имя этого свойства объекта. Когда я пишу это 11 мая 2015 года, никакой другой движок JavaScript, кроме "Project Spartan" от Microsoft для Windows 10 Preview, не поддерживает это (да, вы правильно поняли, M$ попал туда раньше, чем в Mozilla или Google), но он в спецификация и реализации наверстают упущенное. Обновление: по состоянию на октябрь 2016 года в последних версиях Chrome реализована Function#name и различные правила для присвоения имен; Firefox по-прежнему нет, но они туда доберутся.

Так, например, в ES2015 это создает функцию с именем "foo###", где ### это 1-3 цифры:

const dynamicName = "foo" + Math.floor(Math.random() * 1000);
const obj = {
  [dynamicName]() {
    throw new Error();
  }
};
const f = obj[dynamicName];
// See its `name` property (Edge and Chrome for now, eventually Firefox will get it)
console.log("Function's `name` property: " + f.name + " (see compatibility note)");
// We can see that it truly has a name (even in Firefox which doesn't have the `name` property yet) via an exception:
try {
  f();
} catch (e) {
  console.log(e.stack);
}

Это использует новое оцененное имя свойства ES2015 и новый синтаксис метода, чтобы создать функцию и дать ей динамическое имя, а затем получить ссылку на нее вf, (Это также будет работать с[dynamicName]: function() { }, синтаксис метода не требуется, синтаксис функции в порядке.)


Ответ для ECMAScript 5(с 2012 года):

Нет. Вы не можете сделать это безevalили его двоюродный братFunctionконструктор. Ваш выбор:

  1. Вместо этого живите с анонимной функцией. Современные движки делают что-то, чтобы помочь в их устранении.

  2. использованиеeval,

  3. ИспользоватьFunctionконструктор.

Подробности:

  1. Вместо этого живите с анонимной функцией. Многие современные движки покажут полезное имя (например, в стеках вызовов и тому подобное), если у вас есть хороший, однозначныйvar name = function() { ... }; выражение (показывающее имя переменной), хотятехнически функция не имеет имени. В ES6 функции, созданные таким образом, на самом деле будут иметь имена, если они могут быть выведены из контекста. В любом случае, если вы хотите действительно определенное во время выполнения имя (имя, полученное из переменной), вы в значительной степени застряли.

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

    Вот какevalвариант выглядит:

    var name = /* ...come up with the name... */;
    var f = eval(
        "(function() {\n" +
        "   function " + name + "() {\n" +
        "       console.log('Hi');\n" +
        "   }\n" +
        "   return " + name + ";\n" +
        "})();"
    );
    

    Живой пример| Живой источник

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

    Это не использовалось, чтобы правильно назначить имя (удивительно) в более старых версиях Firefox. Начиная с текущей версии их движка JavaScript в Firefox 29, это так.

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

    (function() {
        function display(msg) {
            var p = document.createElement('p');
            p.innerHTML = String(msg);
            document.body.appendChild(p);
        }
    
        var name = /* ...come up with the name... */;
        var f = eval(
            "(function() {\n" +
            "   function " + name + "() {\n" +
            "       display('Hi');\n" +         // <=== Change here to use the
            "   }\n" +                          //      function above
            "   return " + name + ";\n" +
            "})();"
        );
    })();
    
  3. ИспользоватьFunctionконструктор, как показано в этой статье Маркос Касерес:

    var f = new Function(
        "return function " + name + "() {\n" +
        "    display('Hi!');\n" +
        "    debugger;\n" +
        "};"
    )();
    

    Живой пример| Живой источник

    Там мы создаем временную анонимную функцию (созданную черезFunction конструктор) и назовите это; эта временная анонимная функция создает именованную функцию, используя выражение именованной функции. Это вызовет некорректный дескриптор выражений именованных функций в IE8 и более ранних версиях, но это не имеет значения, потому что побочные эффекты этого ограничены временной функцией.

    Это короче eval версия, но есть проблема: функции, созданные с помощью Function Конструктор не имеет доступа к области, в которой они были созданы. Таким образом, приведенный выше пример использования display потерпит неудачу, потому что display не будет в рамках созданной функции. ( Вот пример неудачи. Источник). Так что не вариант для аккуратных кодеров, избегающих глобальных символов, но полезен для тех случаев, когда вы хотите отделить сгенерированную функцию от области, в которой вы ее генерируете.

Вот полезная функция, которую я придумал некоторое время назад. Он использует Function метод конструктора, как описано в отличном ответе @TJCrowder, но устраняет его недостатки и позволяет детально контролировать область действия новой функции.

function NamedFunction(name, args, body, scope, values) {
    if (typeof args == "string")
        values = scope, scope = body, body = args, args = [];
    if (!Array.isArray(scope) || !Array.isArray(values)) {
        if (typeof scope == "object") {
            var keys = Object.keys(scope);
            values = keys.map(function(p) { return scope[p]; });
            scope = keys;
        } else {
            values = [];
            scope = [];
        }
    }
    return Function(scope, "function "+name+"("+args.join(", ")+") {\n"+body+"\n}\nreturn "+name+";").apply(null, values);
};

Это позволяет вам быть аккуратным и избежать полного доступа к вашей области через evalНапример, в приведенном выше сценарии:

var f = NamedFunction("fancyname", ["hi"], "display(hi);", {display:display});
f.toString(); // "function fancyname(hi) {
              // display(hi);
              // }"
f("Hi");

Также

      let f = function test(){};
Object.defineProperty(f, "name", { value: "New Name" });

Выполнит то же самое, что и принятый ответ

console.log(f.name) // New Name

однако ни один из них не показывает "Новое имя" при печати функции console.log(f) // test(){}

Другие вопросы по тегам