JavaScript中的动态函数名称?


87

我有这个:

this.f = function instance(){};

我想要这个:

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

10
你不能 但您可以拥有this["instance"] = function() { }
雷诺斯(Raynos)2011年


为了弄清楚雷诺斯在说什么,您可以做this["instance" + a] = function() { }。我不清楚。
安德鲁(Andrew)

Answers:


7

更新

正如其他人提到的那样,这不是最快也不推荐的解决方案。Marcosc下面的解决方案解决之道

您可以使用eval:

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

1
这就是我要的,谢谢!(无论如何,我不会使用此功能,因为它太慢了)
Totty.js,2011年

3
我知道这是OP要求的,但这是一个可怕的想法。仅仅因为您可以做并不意味着您应该做这样的事情。在功能上几乎有完全相同的更好的替代方法。
Thomas Eding

4
@ sg3s:您可以提出其他解决方案吗?
汤姆

2
@ sg3s:感谢您回答我的评论!让我解释一下我的意思,以及我实际上要问的问题:Marcosc的解决方案与eval确实有很大不同吗?如果您评估或将其提供给Function构造函数,是否真的有所不同?如果是这样,您能解释一下差异及其后果吗?谢谢!
汤姆

5
@Tom对于将来的读者:此解决方案与Marcosc的答案并没有真正的不同,它们都使用eval()Function构造函数在内部执行此操作)。
卡帕2015年

126

基本上,这将在最简单的级别上完成:

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

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

如果您想花更多的钱,我写了一篇有关“ JavaScript中的动态函数名称”的文章。


干得好!我只是自己发现了这个问题,并打算将其发布在其中一个问题上,但是您击败了我。最近,我一直在与steel.js进行大量合作,并且厌倦了在Chrome调试器中到处看到“孩子”的情况。这解决了这个问题。我想知道是否存在使用eval之类的性能影响。
webXL 2012年

也有安全隐患。我从jsHint获得“函数构造函数为eval”,因此将其包装在调试模式检查中,因为这是使用此命令的唯一原因。我猜想“使用严格”将防止对全局对象的任何破坏,但是可以修改函数构造函数的任何参数,以及将“ this”设置为什么。
webXL 2012年

是的,这是在极端情况下您需要即时构建某些东西的情况。
Marcosc

1
老实说,我认为stackoverflow.com/a/40918734/2911851是更好的解决方案,不需要将函数的主体包含为字符串(除非我遗漏了一些东西,但我只是尝试并工作得很好)。
吉安·佛朗哥·扎巴里诺'17

2
AirBnB强烈建议不要这样做,因为Function构造函数将用于eval评估javascript-从而使您的代码暴露出一系列漏洞。
菲利普·赫伯特

42

您可以使用MDN JavaScript参考[1]中所述的Object.defineProperty:

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

2
按预期显示该组的名称属性,但如果我登录我的浏览器功能(最新的Firefox开发版)它打印function fn()fn是原来的名称。奇怪的。
Kiara Grouwstra

对谷歌浏览器常绿的相同评论。属性已正确设置,但控制台中的名称是该函数的原始名称。cf. 2ality.com/2015/09/… 函数的名称似乎是在创建时分配的,无法更改?
user3743222 '18

在2ality.com页面上,请参见下一段“更改函数的名称” [1],其中描述了与MDN Javascript Reference中相同的技术。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

它与转译代码一起工作吗?(Babel或打字稿输出)
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/…
Jason Young,

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 TS

11

我认为,通过使用评估,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”的函数),也就是类。
Albin

@Albin FWIW,我完全不清楚您的回答是怎么说的。无论哪种方式,都知道。
詹森·杨

@JasonYoung“抵制创建命名函数工厂方法的诱惑,因为您将无法从外部传递函数并将其改型为语法位置以推断其名称。然后为时已晚。如果您确实需要它,您可以必须创建一个包装器。有人在这里做了,但是该解决方案不适用于类(也是函数)。”
阿尔宾(Albin)

3

关于什么

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

唯一的缺点是其toSource方法中的函数不会指示名称。对于调试器而言,通常这只是一个问题。


那不是很好,因为我唯一需要的是更快地查看类的名称。在我的系统中的每个类是一个匿名函数并在调试器显示我匿名..
Totty.js

1
好吧,那么您可以在问题中这么说。
entonio 2012年

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引用了ananonymous function和not 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]函数体。我一直在寻找重命名已经声明的函数名称的解决方案,最后经过一个小时的努力,我已经解决了它。它:

  • 接受已声明的功能
  • 使用.toString()方法将其解析为[String]
  • 然后覆盖(命名函数的名称或在和之间添加新的名称匿名时)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>>(){}
Sancarn

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要求提供“动态”功能名称。这是做到这一点的一种方法,但是我从不推荐,也从未这样做。
OnurYıldırım18年

1

有两种方法可以实现此目的,它们各有利弊。


name 属性定义

定义函数的不可变name 属性

优点

  • 名称可以使用每个字符。(例如() 全 {}/1/얏호/ :D #GO(@*#%! /*

缺点

  • 函数的句法(“表达式”)名称可能与其name属性值不对应。

函数表达式评估

制作命名函数表达式并使用构造函数对其进行求值Function

优点

  • 函数的句法(“表达式”)名称始终与其name属性值相对应。

缺点

  • 名称不能使用空格(等)。
  • 可注入表达式(例如,对于input (){}/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

如果您想要具有动态功能(如__callPHP中的功能),则可以使用Proxies。

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

谢谢Marcosc!基于他的答案,如果您想重命名任何功能,请使用以下命令:

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

我最好把Darren的答案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与Function.name 1的标准ES2015规范匹配

这尤其在获取周围的WebPack类似的错误帮助这一个

更新:我当时想将其发布为npm软件包,但sindresorhus的此软件包的作用完全相同。

  1. https://developer.mozilla.org/zh-CN/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非常有用,因为现在可以从变量和属性中推断出它。它也用于堆栈跟踪。防爆var fn = function(){}; console.log(fn.name)。它是不可变的,因此以后不能更改。如果您编写了一个工厂方法来命名所有函数,fn那么这将使调试更加困难。
Albin

-3

您在附近:

this["instance_" + a] = function () {...};

{...};


-9

这是最好的解决方案,那就更好了new Function('return function name(){}')()

评估是最快的解决方案:

在此处输入图片说明

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

任何正在阅读此内容的人,请按照以下说明进行操作。
Maxmaxmaximus

1
@majidarif这个人是正确的,它是最快的:jsperf.com/dynamicfunctionnames/1。到目前为止并不是最安全的。
Sancarn
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.