如何在我只知道函数名的情况下执行JavaScript函数

1273

我有一个JavaScript函数名的字符串,如何将其转换为函数指针以便稍后调用?

根据情况,我可能需要将各种参数传递到方法中。

一些函数可能采用namespace.namespace.function(args[...])的形式。

37个回答

1657

除非你绝对没有其他选择,否则不要使用eval

正如先前提到的,使用以下内容将是最好的方法:

window["functionName"](arguments);

然而,对于一个带有命名空间的函数,这种方法不起作用:

window["My.Namespace.functionName"](arguments); // fail

这是如何做到的:

window["My"]["Namespace"]["functionName"](arguments); // succeeds
为了使这一过程更加容易并提供一定的灵活性,这里提供了一个便利函数:
function executeFunctionByName(functionName, context /*, args */) {
  var args = Array.prototype.slice.call(arguments, 2);
  var namespaces = functionName.split(".");
  var func = namespaces.pop();
  for(var i = 0; i < namespaces.length; i++) {
    context = context[namespaces[i]];
  }
  return context[func].apply(context, args);
}

你可以这样调用它:

executeFunctionByName("My.Namespace.functionName", window, arguments);

请注意,您可以传递任何所需的上下文,因此这将与上述内容相同:

executeFunctionByName("Namespace.functionName", My, arguments);

4
你知道你不需要整个“func”结构吗?仅使用“context.apply”就可以。 - annakata
22
可以,我了解 - 但是我编写函数的方式可以为那些可能不完全理解正在发生的事情的人提供一些清晰度。我编写这个函数时意识到阅读它的人可能需要一些帮助。既然你问了,我会提供一个替代方案... - Jason Bunting
130
放弃这个想法——代码已经足够清晰,那些懂的人就懂了。如果你和我一样知道自己在做什么,那么只要使用这段代码,就可以自己进行这样的更改。Stack Overflow是为了教育他人,我认为我的代码更容易让初学者理解。不过还是谢谢你! - Jason Bunting
4
window["funcName"]可能会返回undefined吗?这就是我目前遇到的问题。 调用代码和函数在两个不同的js文件中定义。我尝试将它们添加到同一个文件中,但没有任何改变。 - codemonkey
6
我认为这里有个问题。当你调用 My.Namespace.functionName() 时,this 将指向 My.Namespace 对象。但是,当你调用 executeFunctionByName("My.Namespace.functionName", window) 时,没有办法使 this 指向相同的对象。也许它应该使用最后一个命名空间作为作用域,或者如果没有命名空间,则使用 window。或者你可以允许用户将作用域指定为参数。 - JW.
显示剩余28条评论

112

我想分享一下稍微修改过的Jason Bunting非常有用的函数

首先,我通过向slice()提供第二个参数来简化了第一个语句。原始版本在除IE外的所有浏览器中都可以正常工作。

其次,在返回语句中,我用context替换了this;否则,在执行目标函数时,this总是指向window

function executeFunctionByName(functionName, context /*, args */) {
    var args = Array.prototype.slice.call(arguments, 2);
    var namespaces = functionName.split(".");
    var func = namespaces.pop();
    for (var i = 0; i < namespaces.length; i++) {
        context = context[namespaces[i]];
    }
    return context[func].apply(context, args);
}

“是否有检查确认“functionName”实际存在的代码?” - Crashalot
我认为Mac的答案被低估了。虽然我不是专家,但它看起来经过深思熟虑且健壮可靠。 - Martin Hansen Lennox
你也可以只返回 context[func];,让调用者传递参数: getFuncByName(functionName, context)(arg1,arg2) 这样它就会使用与 eval 相同的定义。 - Omu

86
这个其他问题的答案向您展示了如何实现: Python的locals()在JavaScript中的等效方法? 基本上,您可以这样说:
window["foo"](arg1, arg2);

或者像许多其他人建议的那样,您可以使用eval:

eval(fname)(arg1, arg2);

虽然这样做是极其不安全的,除非您完全确定 eval 的内容。

8
第一种形式更可取。 - annakata
28
只有在其他方法都失败时,才使用eval作为最后的手段。 - Jason Bunting
2
这很...但它能与像这样的函数一起使用吗:x.y.z(args)? - Kieron
@keiron:是的。请看下面我的回答。 - annakata
1
window选项在像Node这样的服务器环境中根本不起作用。 在这种情况下,eval可能是最好和最快的选择。 - Jacopo Pace

84

我认为一种优雅的方法是通过在哈希对象中定义函数。然后,您可以使用字符串从哈希中引用这些函数。

var customObject = {
  customFunction: function(param){...}
};

然后您可以调用:

customObject['customFunction'](param);

自定义函数将是一个字符串,与您的对象中定义的函数匹配。

更新

看起来这个答案对很多程序员有帮助,因此这里提供一个更新版本。

使用ES6,您还可以使用计算属性名,这将使您避免使用魔术字符串。

const FunctionNames = Object.freeze({ 
  FirstFunction: "firstFunction", 
  SecondFunction: "secondFunction" 
});

...

var customObject = {
  [FunctionNames.FirstFunction]: function(param){...},
  [FunctionNames.SecondFunction]: function(param){...}
};

...

customObject[FunctionNames.FirstFunction](param);


@ibsenv,谢谢你的评论,帮助我确定这个回答是最好的。我创建了一个函数对象的数组,并依次使用它来创建延迟承诺的数组。我在下面放了一些示例代码。(我不想创建一个新的回复并“借”Ruben的回复。) - user216661
function getMyData(arrayOfObjectsWithIds) { var functionArray = arrayOfObjectsWithIds.map(function (value) { return { myGetDataFunction: MyService.getMyData(value.id) }; }); var promises = functionArray.map(function (getDataFunction) { var deferred = $q.defer(); getDataFunction.myGetDataFunction.success(function (data) { deferred.resolve(data); }).error(function (error) { deferred.reject(); }); return deferred.promise; }); $q.all(promises).then(function (dataArray) { //do stuff }); }; - user216661
这个非常好用,我只是添加了下划线/ lodash 来验证它是否为函数。然后运行。 - elporfirio

63

你不能只是这样做吗:

var codeToExecute = "My.Namespace.functionName()";
var tmpFunc = new Function(codeToExecute);
tmpFunc();

您可以使用此方法执行任何其他JavaScript代码。

3
当使用函数时,即使传递偶数个参数,它也能正常工作。 - adeel41
13
eval("My.Namespace.functionName()"); 相比,这有何不同? - brettwhiteman
1
@PeterDenev 只需将第一行更改为 var codeToExecute = "return My.Namespace.functionName()"; - brettwhiteman
2
@developerbmw,这里是答案:https://dev59.com/GG445IYBdhLWcg3w_PG3 - Tejasvi Hegde
1
使用Function()构造函数并不是一个好主意(它和eval()一样糟糕),因为代码被传递为字符串并进行评估。——《JavaScript模式》,第4章,第58页 - domih
显示剩余5条评论

61

使用 ES6,您可以通过名称访问类方法:

class X {
  method1(){
    console.log("1");
  }
  method2(){
    this['method1']();
    console.log("2");
  }
}
let x  = new X();
x['method2']();

输出结果将会是:

1
2

2
最好的纯JavaScript...天啊..删除类不起作用但没关系。谢谢! - KingRider
2
这就是我长期寻找的东西。谢谢! - PaladiN
1
ES2015在这里没有任何作用。您可以使用纯对象或通过Object.create()进行原型委托来实现相同的目标。const myObj = { method1() { console.log('1') }, method2() { console.log('2') } } myObj'method1'; // 1 myObj'method2'; // 2 - sminutoli
1
这太棒了!!!我很惊讶以前从未想过这个。不错!!! - thxmike
如果你想使用一个变量,例如 let y = 'method2'; xy;,你必须为类X声明一个索引类型 {[key: string]: any;...否则你会得到一个错误:»Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'X'. No index signature with a parameter of type 'string' was found on type 'X'.« - Denis Giffeler
显示剩余2条评论

30

两件事:

  • 避免使用eval,它非常危险且缓慢。

  • 其次,函数存在的位置并不重要,“全局”性质是无关紧要的。“x.y.foo()”可以通过“x.y['foo']()”或“x['y']['foo']()”甚至“window['x']['y']['foo']()”来启用。您可以像这样无限链接。


1
但是你不能使用window'x.y.z'来调用x.y.z()。 - nickf

28

所有答案都假设函数可以通过全局作用域(window)访问。然而,OP没有做出这个假设。

如果函数存在于本地作用域(也称为闭包)中且未被其他本地对象引用,则很不幸:您必须使用eval(),据我所知,请参见在JavaScript中动态调用本地函数


5
兄弟(或姐妹),非常感谢你指出这一点!我一度以为自己疯了。 - Funktr0n
如果它在本地对象中,你可以直接这样做:localobject'function_name',与window'function_name'相同。 - Kyobul
如果你仍然喜欢使用窗口的第一种方法,你可以通过定义它来使窗口可以访问本地函数。function myLocalOwn() {}; window.myLocalOwn = myLocalOwn; - undefined

25

根据您所在的位置,您也可以使用:

this["funcname"]();
self["funcname"]();
window["funcname"]();
top["funcname"]();
globalThis["funcname"]();

或者,在nodejs中

global["funcname"]()

感谢这个答案,可以使用function callObjectMethod(obj,meth){ return (_v) => { obj[meth](_v) } }。对我来说,这对于使用通过外部服务回调的参数调用某些对象方法非常有用。希望这能帮助其他人。 - Enrique René

15

这是我的对Jason Bunting / Alex Nazarov优秀答案的贡献,在其中我包含了Crashalot请求的错误检查。

考虑到这个(虚构的)前提:

a = function( args ) {
    console.log( 'global func passed:' );
    for( var i = 0; i < arguments.length; i++ ) {
        console.log( '-> ' + arguments[ i ] );
    }
};
ns = {};
ns.a = function( args ) {
    console.log( 'namespace func passed:' );
    for( var i = 0; i < arguments.length; i++ ) {
        console.log( '-> ' + arguments[ i ] ); 
    }
};
name = 'nsa';
n_s_a = [ 'Snowden' ];
noSuchAgency = function(){};

然后是下面的函数:

function executeFunctionByName( functionName, context /*, args */ ) {
    var args, namespaces, func;

    if( typeof functionName === 'undefined' ) { throw 'function name not specified'; }

    if( typeof eval( functionName ) !== 'function' ) { throw functionName + ' is not a function'; }

    if( typeof context !== 'undefined' ) { 
        if( typeof context === 'object' && context instanceof Array === false ) { 
            if( typeof context[ functionName ] !== 'function' ) {
                throw context + '.' + functionName + ' is not a function';
            }
            args = Array.prototype.slice.call( arguments, 2 );

        } else {
            args = Array.prototype.slice.call( arguments, 1 );
            context = window;
        }

    } else {
        context = window;
    }

    namespaces = functionName.split( "." );
    func = namespaces.pop();

    for( var i = 0; i < namespaces.length; i++ ) {
        context = context[ namespaces[ i ] ];
    }

    return context[ func ].apply( context, args );
}

这将允许您通过存储在字符串中的名称(包括带有命名空间或全局的名称),调用JavaScript函数,带或不带参数(包括数组对象),并提供有关遇到的任何错误的反馈(希望能捕获它们)。

示例输出显示了它如何工作:

// calling a global function without parms
executeFunctionByName( 'a' );
  /* OUTPUT:
  global func passed:
  */

// calling a global function passing a number (with implicit window context)
executeFunctionByName( 'a', 123 );
  /* OUTPUT:
  global func passed:
  -> 123
  */

// calling a namespaced function without parms
executeFunctionByName( 'ns.a' );
  /* OUTPUT:
  namespace func passed:
  */

// calling a namespaced function passing a string literal
executeFunctionByName( 'ns.a', 'No Such Agency!' );
  /* OUTPUT:
  namespace func passed:
  -> No Such Agency!
  */

// calling a namespaced function, with explicit context as separate arg, passing a string literal and array 
executeFunctionByName( 'a', ns, 'No Such Agency!', [ 007, 'is the man' ] );
  /* OUTPUT:
  namespace func passed:
  -> No Such Agency!
  -> 7,is the man
  */

// calling a global function passing a string variable (with implicit window context)
executeFunctionByName( 'a', name );
  /* OUTPUT:
  global func passed:
  -> nsa
  */

// calling a non-existing function via string literal
executeFunctionByName( 'n_s_a' );
  /* OUTPUT:
  Uncaught n_s_a is not a function
  */

// calling a non-existing function by string variable
executeFunctionByName( n_s_a );
  /* OUTPUT:
  Uncaught Snowden is not a function
  */

// calling an existing function with the wrong namespace reference
executeFunctionByName( 'a', {} );
  /* OUTPUT:
  Uncaught [object Object].a is not a function
  */

// calling no function
executeFunctionByName();
  /* OUTPUT:
  Uncaught function name not specified
  */

// calling by empty string
executeFunctionByName( '' );
  /* OUTPUT:
  Uncaught  is not a function
  */

// calling an existing global function with a namespace reference
executeFunctionByName( 'noSuchAgency', ns );
  /* OUTPUT:
  Uncaught [object Object].noSuchAgency is not a function
  */

不知道...这是非常好的努力,毫无疑问。但对我来说听起来有点“过于宽泛”了... - Niki Romagnoli
2
什么?SO是一个问题/答案/教学平台。我很乐意提供我能想到的所有例子,希望能传达启示。对我来说,这就是重点。 - Mac
如果您已经在评估 functionName,为什么不直接使用它呢? - data
这对我不起作用。我有一个命名空间函数a.b.c.d,其中d是函数名称。调用executeFunctionByName(“a.b.c.d”,window)在检查if(typeof context [functionName]!== 'function')的行上失败,因为上下文 - 窗口 - 被定义为对象和数组,但window ['a.b.c.d']不存在,正如在接受的答案中所确定的问题:“window [“ My.Namespace.functionName”](arguments); //失败”。 - akousmata

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接