提问人:T.J. Crowder 提问时间:2/28/2012 更新时间:10/1/2023 访问量:8574
是否有任何非评估方法可以创建具有运行时确定名称的函数?
Is there any non-eval way to create a function with a runtime-determined name?
问:
有没有办法创建一个在运行时确定的真实名称的函数,而无需使用 ,并且只使用纯 JavaScript?(因此,没有生成的元素,因为这些元素是特定于浏览器环境的[并且在许多方面无论如何都是伪装的];不要使用某个特定JavaScript引擎的非标准功能,等等)eval
script
eval
请注意,我不是特别询问由具有名称的变量或属性引用的匿名函数,例如:
// NOT this
var name = /* ...come up with the name... */;
var obj = {};
obj[name] = function() { /* ... */ };
在那里,虽然 object 属性有名称,但函数没有。匿名函数在很多事情上都很好,但不是我在这里寻找的。我希望函数有一个名称(例如,显示在调试器的调用堆栈中等)。
答:
ECMAScript 2015+(又名“ES6”)的答案:
是的。从 ES2015 开始,由分配给对象属性的匿名函数表达式创建的函数采用该对象属性的名称。这是在所有现代浏览器中实现的,尽管 Edge 和 Safari 在堆栈跟踪中不使用该名称。我们可以将其与另一个 ES2015 功能(计算属性名称)结合使用,以命名一个不带 or 的函数。new Function
eval
在 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
console.log(
"Function's `name` property: " + f.name + " (see compatibility note)"
);
// We can see whether it has a name in stack traces via an exception
try {
f();
} catch (e) {
console.log(e.stack);
}
它也可以与 ,不需要方法语法,函数语法很好。如果你想以这种方式创建一个构造函数,这很方便:[dynamicName]: function() { }
const dynamicName = "Foo" + Math.floor(Math.random() * 1000);
const obj = {
[dynamicName]: function (throwError = false) {
if (throwError) {
throw new Error();
}
},
};
const F = obj[dynamicName];
// See its `name` property
console.log(
"Function's `name` property: " + F.name + " (see compatibility note)"
);
// We can see whether it has a name in stack traces via an exception
try {
new F(true);
} catch (e) {
console.log(e.stack);
}
// And we can see it works as a constructor:
const inst = new F();
console.log(inst instanceof F); // true
当然,这是 ES2015+,所以你也可以用它来创建一个构造函数:class
[dynamicName]: class { }
const dynamicName = "Foo" + Math.floor(Math.random() * 1000);
const obj = {
[dynamicName]: class {
constructor(throwError = false) {
if (throwError) {
throw new Error();
}
}
},
};
const F = obj[dynamicName];
// See its `name` property
console.log(
"Function's `name` property: " + F.name + " (see compatibility note)"
);
// We can see whether it has a name in stack traces via an exception
try {
new F(true);
} catch (e) {
console.log(e.stack);
}
// And we can see it works as a constructor:
const inst = new F();
console.log(inst instanceof F); // true
ECMAScript 5 的答案(自 2012 年起):
不。如果没有构造函数或其表亲构造函数,就无法做到这一点。您的选择是:eval
Function
改用匿名函数。现代引擎会做一些事情来帮助调试这些。
用。
eval
使用构造函数。
Function
详:
改用匿名函数。如果你有一个漂亮的、明确的表达式(显示变量的名称),许多现代引擎都会显示一个有用的名称(例如,在调用堆栈等中),即使从技术上讲,该函数没有名称。(在 ES2015+ 中,以这种方式创建的函数实际上会有名称。但是,无论哪种方式,如果你想要一个真正的运行时定义的名称(来自变量的名称),你几乎被卡住了。
var name = function() { ... };
用。 当你可以避免它时,它是邪恶的,但是对于你完全控制的字符串,在你控制的范围内,了解成本(你正在启动一个 JavaScript 解析器),做一些你不能做的事情(就像在这种情况下一样),只要你真的需要做这件事,这很好。但是,如果你无法控制字符串或作用域,或者你不想要成本,你将不得不忍受一个匿名函数。
eval
eval
该选项的外观如下:
eval
// Remember, this is old-style ES5 code var name = "foo" + Math.floor(Math.random() * 1000); var f = eval( "(function() {\n" + " function " + name + "() {\n" + " console.log('Hi');\n" + " }\n" + " return " + name + ";\n" + "})();" ); console.log(f.name); f();
这将创建一个具有我们在运行时提供的名称的函数,而不会将该名称泄漏到包含的作用域中,将对该函数的引用分配给 .(而且它很好地格式化了代码,因此在调试器中单步执行代码很容易。
f
这在旧版本的Firefox中并没有正确地分配名称(令人惊讶)。从Firefox 29中JavaScript引擎的当前版本开始,它确实如此。
因为这使用 ,您创建的函数可以访问创建它的范围,如果您是一个避免使用全局符号的整洁编码人员,这一点很重要。因此,这有效,例如:
eval
// Remember, this is old-style ES5 code (function() { function display(msg) { console.log(msg); } var name = "foo" + Math.floor(Math.random() * 1000); var f = eval( "(function() {\n" + " function " + name + "() {\n" + " display('Hi');\n" + // <=== Uses the " }\n" + // function above " return " + name + ";\n" + "})();" ); console.log(f.name); f(); })(); console.log(typeof display); // undefined (because it's not a global)
使用构造函数,如 Marcos Cáceres 的这篇文章中所示:
Function
// Remember, this is old-style ES5 code var name = "foo" + Math.floor(Math.random() * 1000); var f = new Function( "return function " + name + "() {\n" + " console.log('Hi!');\n" + "};" )(); console.log(f.name); f();
在那里,我们创建一个临时匿名函数(通过构造函数创建的函数)并调用它;该临时匿名函数使用命名函数表达式创建命名函数。
Function
这比版本短,并且通过构造函数创建的函数无权访问创建它们的范围。因此,上面的示例将失败,因为不会在创建的函数的范围内。因此,对于避免全局符号的整洁编码人员来说,这不是一个选项,但在您想要将生成的函数与生成函数的范围取消关联时很有用。
eval
Function
display
display
评论
new Function
eval
try/catch
new Function
eval
new Function
function replaceHere(){...}.toString()
这是我前段时间想出的一个实用函数。它使用了 @T.J.Crowder 的精彩回答中概述的构造函数技术,但改进了其缺点,并允许对新函数的范围进行细粒度控制。Function
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(){}
评论
console.log
console.log('%s', f)
下一个:从对象中删除项目而不转换数组
评论