簡體   English   中英

當我將其名稱作為字符串時如何執行 JavaScript function

[英]How to execute a JavaScript function when I have its name as a string

我在 JavaScript 中有一個 function 作為字符串的名稱。 如何將其轉換為 function 指針以便稍后調用它?

根據具體情況,我可能也需要將各種 arguments 傳遞到方法中。

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

不要使用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);

只是想我會發布一個稍微改動過的Jason Bunting 非常有用的功能的版本

首先,我通過向slice()提供第二個參數簡化了第一個語句。 原始版本在除 IE 之外的所有瀏覽器中都能正常工作。

其次,我在 return 語句中用上下文替換了 否則,當執行目標函數時, 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);
}

另一個問題的答案向您展示了如何做到這一點: Javascript 等效於 Python 的 locals()?

基本上,你可以說

window["foo"](arg1, arg2);

或者正如許多其他人所建議的那樣,您可以只使用 eval:

eval(fname)(arg1, arg2);

盡管這是非常不安全的,除非您絕對確定要評估的內容。

我認為這樣做的一種優雅方法是在哈希對象中定義您的函數。 然后,您可以使用字符串從散列中引用這些函數。 例如

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

然后你可以調用:

customObject['customFunction'](param);

其中 customFunction 將是匹配對象中定義的函數的字符串。

更新

似乎這個答案對那里的許多編碼人員很有幫助,所以這里有一個更新的版本。

使用 ES6,您還可以使用計算屬性名稱,這將允許您避免魔術字符串。

 const FunctionNames = Object.freeze({ FirstFunction: "firstFunction", SecondFunction: "secondFunction" }); ... var customObject = { [FunctionNames.FirstFunction]: function(param){...}, [FunctionNames.SecondFunction]: function(param){...} }; ... customObject[FunctionNames.FirstFunction](param);

你能不能不這樣做:

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

您還可以使用此方法執行任何其他 JavaScript。

使用 ES6,您可以按名稱訪問類方法:

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

輸出將是:

1
2

兩件事情:

  • 避免使用 eval,它非常危險且緩慢

  • 其次,你的函數在哪里並不重要,“全局”是無關緊要的。 xyfoo()可以通過xy['foo']()x['y']['foo']()甚至window['x']['y']['foo']()啟用。 你可以像這樣無限期地鏈接。

所有答案都假設可以通過全局范圍(窗口)訪問這些函數。 但是,OP 沒有做出這個假設。

如果函數存在於局部作用域(又名閉包)中並且未被其他局部對象引用,那么運氣不好:您必須使用eval() AFAIK,請參閱在 javascript 中動態調用本地函數

根據您所在的位置,您還可以使用:

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

或者,在 nodejs 中

global["funcname"]()

您只需要通過window[<method name>]將字符串轉換為指針。 例子:

var function_name = "string";
function_name = window[function_name];

現在你可以像指針一樣使用它。

這是我對 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 函數,命名空間或全局,帶或不帶參數(包括 Array 對象),提供對遇到的任何錯誤的反饋(希望能捕獲它們)。

示例輸出顯示了它的工作原理:

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

如果要使用window["functionName"]調用對象的函數而不是全局函數。 你可以這樣做;

var myObject=new Object();
myObject["functionName"](arguments);

例子:

var now=new Date();
now["getFullYear"]()

當心!!!

出於兩個原因,應該盡量避免在 JavaScript 中通過字符串調用函數:

原因 1:一些代碼混淆器會破壞您的代碼,因為它們會更改函數名稱,使字符串無效。

原因 2:維護使用這種方法的代碼要困難得多,因為查找字符串調用的方法的用法要困難得多。

這是我的 Es6 方法,它使您可以通過名稱作為字符串或函數名稱來調用函數,還可以將不同數量的參數傳遞給不同類型的函數:

 function fnCall(fn, ...args) { let func = (typeof fn =="string")?window[fn]:fn; if (typeof func == "function") func(...args); else throw new Error(`${fn} is Not a function!`); } function example1(arg1){console.log(arg1)} function example2(arg1, arg2){console.log(arg1 + " and " + arg2)} function example3(){console.log("No arguments!")} fnCall("example1", "test_1"); fnCall("example2", "test_2", "test3"); fnCall(example3); fnCall("example4"); // should raise an error in console

驚訝地看到沒有提到 setTimeout。

要運行不帶參數的函數:

var functionWithoutArguments = function(){
    console.log("Executing functionWithoutArguments");
}
setTimeout("functionWithoutArguments()", 0);

要運行帶參數的函數:

var functionWithArguments = function(arg1, arg2) {
    console.log("Executing functionWithArguments", arg1, arg2);
}
setTimeout("functionWithArguments(10, 20)");

要運行深度命名空間函數:

var _very = {
    _deeply: {
        _defined: {
            _function: function(num1, num2) {
                console.log("Execution _very _deeply _defined _function : ", num1, num2);
            }
        }
    }
}
setTimeout("_very._deeply._defined._function(40,50)", 0);

我認為您不需要復雜的中間函數或 eval 或依賴於像 window 這樣的全局變量:

function fun1(arg) {
  console.log(arg);
}

function fun2(arg) {
  console.log(arg);
}

const operations = {
  fun1,
  fun2
};

operations["fun1"]("Hello World");
operations.fun2("Hello World");

// You can use intermediate variables, if you like
let temp = "fun1";
operations[temp]("Hello World");

它也適用於導入的函數:

// mode.js
export function fun1(arg) {
  console.log(arg);
}

export function fun2(arg) {
  console.log(arg);
}
// index.js
import { fun1, fun2 } from "./mod";

const operations = {
  fun1,
  fun2
};

operations["fun1"]("Hello World");
operations["fun2"]("Hello World");

由於它使用的是屬性訪問,因此它可以在最小化或混淆中幸存下來,這與您在此處找到的一些答案相反。

所以,就像其他人說的,絕對是最好的選擇:

window['myfunction'](arguments)

就像Jason Bunting 所說的那樣,如果您的函數名稱包含一個對象,它將無法工作:

window['myobject.myfunction'](arguments); // won't work
window['myobject']['myfunction'](arguments); // will work

所以這是我的函數版本,它將按名稱執行所有函數(包括或不包括對象):

 my = { code : { is : { nice : function(a, b){ alert(a + "," + b); } } } }; guy = function(){ alert('awesome'); } function executeFunctionByName(str, args) { var arr = str.split('.'); var fn = window[ arr[0] ]; for (var i = 1; i < arr.length; i++) { fn = fn[ arr[i] ]; } fn.apply(window, args); } executeFunctionByName('my.code.is.nice', ['arg1', 'arg2']); executeFunctionByName('guy');

  let t0 = () => { alert('red0') }
  var t1 = () =>{ alert('red1') }
  var t2 = () =>{ alert('red2') }
  var t3 = () =>{ alert('red3') }
  var t4 = () =>{ alert('red4') }
  var t5 = () =>{ alert('red5') }
  var t6 = () =>{ alert('red6') }

  function getSelection(type) {
    var evalSelection = {
      'title0': t0,
      'title1': t1,
      'title2': t2,
      'title3': t3,
      'title4': t4,
      'title5': t5,
      'title6': t6,
      'default': function() {
        return 'Default';
      }
    };
    return (evalSelection[type] || evalSelection['default'])();
  }
  getSelection('title1');

一個更加面向對象的解決方案......

關於 Jason 和 Alex 的帖子的更多細節。 我發現為上下文添加默認值很有幫助。 只需放置context = context == undefined? window:context; context = context == undefined? window:context; 在函數的開頭。 您可以將window更改為您喜歡的任何上下文,然后每次在默認上下文中調用它時都不需要傳入相同的變量。

要添加到 Jason Bunting 的答案中,如果您使用的是 nodejs 或其他東西(這也適用於 dom js),您可以使用this代替window (並記住:eval 是邪惡的

this['fun'+'ctionName']();

我的代碼中有一個非常相似的東西。 我有一個服務器生成的字符串,其中包含一個函數名稱,我需要將其作為第 3 方庫的回調傳遞。 所以我有一個代碼,它接受字符串並返回一個指向函數的“指針”,如果找不到,則返回 null。

我的解決方案與“ Jason Bunting 的非常有用的功能*非常相似,盡管它不會自動執行,並且上下文始終在窗口上。 但這可以很容易地修改。

希望這會對某人有所幫助。

/**
 * Converts a string containing a function or object method name to a function pointer.
 * @param  string   func
 * @return function
 */
function getFuncFromString(func) {
    // if already a function, return
    if (typeof func === 'function') return func;

    // if string, try to find function or method of object (of "obj.func" format)
    if (typeof func === 'string') {
        if (!func.length) return null;
        var target = window;
        var func = func.split('.');
        while (func.length) {
            var ns = func.shift();
            if (typeof target[ns] === 'undefined') return null;
            target = target[ns];
        }
        if (typeof target === 'function') return target;
    }

    // return null if could not parse
    return null;
}

還有一些非常有用的方法。

http://devlicio.us/blogs/sergio_pereira/archive/2009/02/09/javascript-5-ways-to-call-a-function.aspx

var arrayMaker = {  
    someProperty: 'some value here',  
    make: function (arg1, arg2) {  
        return [ this, arg1, arg2 ];  
    },
    execute: function_name
};

我忍不住要提到另一個技巧,如果您有未知數量的參數也作為包含函數名稱的字符串的一部分傳遞,這會很有幫助 例如:

var annoyingstring = 'call_my_func(123, true, "blah")';

如果您的 Javascript 運行在 HTML 頁面上,您只需要一個不可見的鏈接; 您可以將字符串傳遞給onclick屬性,然后調用click方法。

<a href="#" id="link_secret"><!-- invisible --></a>

$('#link_secret').attr('onclick', annoyingstring);
$('#link_secret').click();

或者在運行時創建<a>元素。

最簡單的方法是像擁有元素一樣訪問它

window.ClientSideValidations.forms.location_form

window.ClientSideValidations.forms['location_form']

人們一直說eval是危險和邪惡的,因為它可以運行任意代碼。 但是,如果您將 eval 與白名單方法一起使用,假設您知道可能需要提前運行的所有可能的函數名稱,那么 eval 不再是一個安全問題,因為輸入不再是任意的 白名單是一種良好且頻繁的安全模式。 下面是一個例子:

 function runDynamicFn(fnName, ...args) { // can also be fed from a tightly controlled config const allowedFnNames = ['fn1', 'ns1.ns2.fn3', 'ns4.fn4']; return allowedFnNames.includes(fnName) ? eval(fnName)(...args) : undefined; } // test function: function fn1(a) { console.log('fn1 called with', a) } runDynamicFn('alert("got you!")') runDynamicFn('fn1', 'foo')

這是我最終為我的一個項目實施的一個有點強大且可重用的解決方案。

FunctionExecutor 構造函數

用法:

let executor = new FunctionExecutor();
executor.addFunction(two)
executor.addFunction(three)

executor.execute("one");
executor.execute("three");

顯然,在項目中,所有需要按名稱調用的函數的添加都是通過循環完成的。

函數執行器:

function FunctionExecutor() {
  this.functions = {};

  this.addFunction = function (fn) {
    let fnName = fn.name;
    this.functions[fnName] = fn;
  }

  this.execute = function execute(fnName, ...args) {
    if (fnName in this.functions && typeof this.functions[fnName] === "function") {
      return this.functions[fnName](...args);
    }
    else {
      console.log("could not find " + fnName + " function");
    }
  }

  this.logFunctions = function () {
    console.log(this.functions);
  }
}

示例用法:

function two() {
  console.log("two"); 
}

function three() {
  console.log("three");
}

let executor = new FunctionExecutor();
executor.addFunction(two)
executor.addFunction(three)

executor.execute("one");
executor.execute("three");

這對我有用:

var command = "Add";
var tempFunction = new Function("Arg1","Arg2", "window." + command + "(Arg1,Arg2)");
tempFunction(x,y);

我希望這有效。

在不使用eval('function()')您可以使用new Function(strName)創建一個新函數。 以下代碼已使用FF,Chrome,IE進行了測試。

<html>
<body>
<button onclick="test()">Try it</button>
</body>
</html>
<script type="text/javascript">

  function test() {
    try {    
        var fnName = "myFunction()";
        var fn = new Function(fnName);
        fn();
      } catch (err) {
        console.log("error:"+err.message);
      }
  }

  function myFunction() {
    console.log('Executing myFunction()');
  }

</script>
use this

function executeFunctionByName(functionName, context /*, args */) {
      var args = [].slice.call(arguments).splice(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);
    }

看基本:

var namefunction = 'jspure'; // String

function jspure(msg1 = '', msg2 = '') { 
  console.log(msg1+(msg2!=''?'/'+msg2:''));
} // multiple argument

// Results ur test
window[namefunction]('hello','hello again'); // something...
eval[namefunction] = 'hello'; // use string or something, but its eval just one argument and not exist multiple

存在其他類型的函數是class並查看示例nils petersohn

感謝您提供非常有幫助的答案。 我在我的項目中使用Jason Bunting 的功能

我將它擴展為使用可選超時,因為設置超時的正常方法不起作用。 查看abhishekisnot 的問題

 function executeFunctionByName(functionName, context, timeout /*, args */ ) { var args = Array.prototype.slice.call(arguments, 3); var namespaces = functionName.split("."); var func = namespaces.pop(); for (var i = 0; i < namespaces.length; i++) { context = context[namespaces[i]]; } var timeoutID = setTimeout( function(){ context[func].apply(context, args)}, timeout ); return timeoutID; } var _very = { _deeply: { _defined: { _function: function(num1, num2) { console.log("Execution _very _deeply _defined _function : ", num1, num2); } } } } console.log('now wait') executeFunctionByName("_very._deeply._defined._function", window, 2000, 40, 50 );

這里有幾個executeByName函數可以正常工作,除非名稱包含方括號- 我遇到的問題 - 因為我有動態生成的名稱。 所以上面的函數會在名稱上失敗,比如

app.widget['872LfCHc']['toggleFolders']

作為一種補救措施,我也制定了功能來考慮這一點,也許有人會發現它很有用:

從 CoffeeScript 生成:

var executeByName = function(name, context) {
  var args, func, i, j, k, len, len1, n, normalizedName, ns;
  if (context == null) {
    context = window;
  }
  args = Array.prototype.slice.call(arguments, 2);
  normalizedName = name.replace(/[\]'"]/g, '').replace(/\[/g, '.');
  ns = normalizedName.split(".");
  func = context;
  for (i = j = 0, len = ns.length; j < len; i = ++j) {
    n = ns[i];
    func = func[n];
  }
  ns.pop();
  for (i = k = 0, len1 = ns.length; k < len1; i = ++k) {
    n = ns[i];
    context = context[n];
  }
  if (typeof func !== 'function') {
    throw new TypeError('Cannot execute function ' + name);
  }
  return func.apply(context, args);
}

為了更好的可讀性,還檢查 CoffeeScript 版本:

executeByName = (name, context = window) ->
    args = Array.prototype.slice.call(arguments, 2)
    normalizedName = name.replace(/[\]'"]/g, '').replace(/\[/g, '.')
    ns = normalizedName.split "."
    func = context
    for n, i in ns
        func = func[n]

    ns.pop()
    for n, i in ns
        context = context[n];
    if typeof func != 'function'
        throw new TypeError 'Cannot execute function ' + name
    func.apply(context, args)

您也可以在eval("functionname as string")調用 javascript 函數。 如下所示:(eval 是純 javascript 函數)

function testfunc(){
    return "hello world";
}

$( document ).ready(function() {

     $("div").html(eval("testfunc"));
});

工作示例: https : //jsfiddle.net/suatatan/24ms0fna/4/

我有一個JavaScript函數的名稱作為字符串。 如何將其轉換為函數指針,以便以后可以調用?

根據具體情況,我可能還需要將各種參數傳遞給該方法。

一些功能可能采用namespace.namespace.function(args[...])

由於eval()是邪惡的,而new Function()不是實現此目的的最有效方法,這里是一個快速的 JS function,它從字符串中的名稱返回 function。

  • 適用於命名空間的功能
  • 在空/未定義字符串的情況下回退到空函數
  • 如果找不到 function,則回退到空函數

    function convertStringtoFunction(functionName){

        var nullFunc = function(){}; // Fallback Null-Function
        var ret = window; // Top level namespace

        // If null/undefined string, then return a Null-Function
        if(functionName==null) return nullFunc;

        // Convert string to function name
        functionName.split('.').forEach(function(key){ ret = ret[key]; });

        // If function name is not available, then return a Null-Function else the actual function
        return (ret==null ? nullFunc : ret);

    }

用法:


    convertStringtoFunction("level1.midLevel.myFunction")(arg1, arg2, ...);

您可以將您的函數名稱放在數組 object 中,然后分別調用每個 function 的數組鍵來執行它, DEMO

function captchaTest(msg){
    let x = Math.floor(Math.random()*(21-1)) +1;
    let y = Math.floor(Math.random()*(11-1)) +1;
    let sum = function(){
        return x+y;
    }
    let sub = function(){
        if (y > x){
            let m = y;
            y = x;
            x = m;
            console.log(x,y,m,'--')
        }
        return x-y;
    }
    let mul = function(){
        return x*y;
    } 
    let OParr = [sum(), sub(), mul()]; 
    let OP = Math.floor(Math.random()*OParr.length);      
    let s = OParr[OP]; //!!! HERE !!! is the call as array element
    switch(OP){
        case 0:
            opra = '+';
            break;
        case 1:
            opra = '━';
            break;
        default:
            opra = '✖';
    }
    let msg2 = 'Answer the following question to continue:'
    let p = prompt(msg+' '+msg2+'\n'+'What is the result of '+x+opra+y+' ?','')
    console.log(s,p,OP)
    if (s == p){
        alert ('Wow, Correct Answer!')
        return true;
    }
    else{
        alert('Sorry, the answer is not correct!')
        return false;
    }
}
  const myFnCollection = {
    myFnStringName: function(args) {}
  };

  let fn = 'myFnStringName';

  // 1. Recommended
  if (typeof window[fn] === 'function') {
    window[fn](args);
  }

  // 2. Recommended
  if (typeof myFnCollection[fn] === 'function') {
    myFnCollection[fn](args);
  }

  // 3. Eval is evil ;)
  if (typeof eval(fn) === 'function') {
    eval(fn)(args);
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM