Есть ли какой-нибудь нереальный способ создать функцию с именем, определенным во время выполнения?
Есть ли способ создать функцию с реальным именем, которое определяется во время выполнения без использования 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
конструктор. Ваш выбор:
Вместо этого живите с анонимной функцией. Современные движки делают что-то, чтобы помочь в их устранении.
использование
eval
,Использовать
Function
конструктор.
Подробности:
Вместо этого живите с анонимной функцией. Многие современные движки покажут полезное имя (например, в стеках вызовов и тому подобное), если у вас есть хороший, однозначный
var name = function() { ... };
выражение (показывающее имя переменной), хотятехнически функция не имеет имени. В ES6 функции, созданные таким образом, на самом деле будут иметь имена, если они могут быть выведены из контекста. В любом случае, если вы хотите действительно определенное во время выполнения имя (имя, полученное из переменной), вы в значительной степени застряли.использование
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" + "})();" ); })();
Использовать
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(){}