5

I've found this topic which I've implemented (see accepted answer):
javascript equivalent of PHP's call_user_func()

However, I am having a problem with multiple parameters. I realize what I was doing was turning my parameters into strings and treating it like 1 parameter, but I don't know how to fix this because I am dynamically creating the parameters.

Meaning, I have defined in my code the following:

var a = new Array();
a[0] = new Array();
a[0][0] = 'alert';
a[0][1] = '\'Hello World\'';
a[1] = new Array();
a[1][0] = 'setTimeout';
a[1][1] = 'alert("goodbye world")';
a[1][2] = '20';

Later, I was calling them like this:

    var j = 0;
    var len = 0;
    var fx = '';
    var params = '';
    for( i in a ){
        params = '';
        len = a[i].length;
        fx = a[i][0]; // getting the function name
        a[i].splice( 0, 1 ); // removing it from array
        if( len > 1 ){
            params = a[i].join(", "); // trying to turn the parameters into the right format, but this is turning it into strings I think
            params = params.replace(/\\'/g,'\''); // bc i was adding slashes with PHP
        }
        window[fx](params);
    }

I don't have to use arrays to do this. I don't understand JS OOP (haven't tried yet), though I am comfortable with PHP OOP, so I don't know if there is a way to do this there.

Any help on passing multiple parameters would be appreciated.

Thanks.

Community
  • 1
  • 1
phpmeh
  • 1,752
  • 2
  • 22
  • 41
  • 1
    `window[fx].apply(obj, params)` – Cheeso Apr 08 '12 at 08:19
  • 2
    You are shooting yourself in the foot here. For starters: You should not have an array that contains code as *strings*, this is just wrong. And don't get me started on interpreting these strings with regex. What are you trying to do? Further: **Never** use a `for .. in` loop on an array, and you should not use `new Array()` at all. – Tomalak Apr 08 '12 at 08:42
  • @Tomalak Sorry but that advice is unsound. JSONP for examples and server-defined callbacks will have functions as strings. It is *safer* to check and call a function by string name than to run an eval. Personally I agree about Regex, but technically it is perfectly viable for the task. Use `for .. in` when appropriate, which is often. Using `new Array()` when you want to create your own containers prototypes is quite handy. – That Realty Programmer Guy Sep 12 '21 at 03:13
  • @GaretClaborn There's always an exception to the rule, that's what defines rules. But this question is not about JSONP, or about constructing your own container prototypes, for that matter. It was a beginner's question, and it's certainly smarter to learn the rules before starting to cut corners. Transferring dynamically created, runnable code to a client for blind execution is still a bad idea, using regex on code is a *horrible* idea, and it's certainly not *safer* (than what, anyway?) to combine those two ideas. – Tomalak Sep 12 '21 at 06:15
  • @Tomalak certainly this *is* a beginner question, as stated and evidenced especially by the array structure/content. That said; characterizing your advice as "the rules", characterizing dynamic code passing as "cut corners", the reference to "blind" execution .. each highly opinionated assumptions, and equally highly debatable. But that debate would go too far off-topic. Using regex on code, while indeed horrible, is significantly safer than using `eval( )` to run code. The technique being attempted, tho just a crude sandbox, provides a level of injection protection. Thus a good exercise. – That Realty Programmer Guy Sep 12 '21 at 09:10
  • @GaretClaborn There is indeed no use debating over this. There are reasons why things like query parameters in SQL exist, or why the use of `eval()` generally levitates somewhere between "frowned upon" and "ruled out", and these reasons are not quite as opinionated as you say. Code injection as an attack form exists only because way too many people wrongly consider themselves smart enough to handle dynamic code safely. Thus "avoid `eval()` until you have fully exhausted *all* other options" a very good general rule. And cutting this corner is for people who are well beyond beginner level. – Tomalak Sep 12 '21 at 09:36
  • @Tomalak no one here has promoted the use of `eval( )`, including the original question. I am not remarking on the general rational behind best practices, I refuted specific reasons presented as short sighted or misleading. Logic external to this topic may of course have valid reasons, this is a non-sequitur – That Realty Programmer Guy Sep 13 '21 at 09:40
  • As it is, the technique used by the OP is better than eval. Like query parameters, though not *as* safe, this will only work for correctly named functions and correctly passed parameters. As a perfectly reasonable attempt at experimenting with reflection this should not be discouraged – That Realty Programmer Guy Sep 13 '21 at 09:46
  • 1
    @GaretClaborn No offense, but I'm really tired this debate, and this thread is almost 10 years old. Your "eval isn't all bad" and "using regex on code makes eval safer" is also nothing but a highly opinionated position. Every piece of programming advice is. Every "rule" is just a convention, usually from people with more experience who found that doing or avoiding certain things is beneficial. Discouraging beginners from writing eval-based solutions when there are other options available is a perfectly reasonable point of view, and I've stated the reasons for that. You're welcome to disagree. – Tomalak Sep 13 '21 at 09:52
  • @Tomalak again, you seem not to understand there is no promoting the use of eval going on. I never said what you ascribe to me. The OP's use of regex, while itself lamentable, still **avoids** the use of eval and is *safer* than eval. – That Realty Programmer Guy Sep 13 '21 at 10:31
  • 1
    Going back to my code 9 years ago is pretty cringy even for me. – phpmeh Sep 13 '21 at 15:40

3 Answers3

14

First thing to do: Scrap your entire code, start over. Your approach will not get you anywhere where you'd want to be. (Unfortunately I can't tell you where you'd want to be because I cannot make sense of your example.)

There are three ways to call a function in JavaScript.

function foo() { console.log(arguments); }

// 1. directly
foo(1, 2, 3);

// 2. trough Function.call()
foo.call(this, 1, 2, 3);

// 3. trough Function.apply()
var args = [1, 2, 3];
foo.apply(this, args);

call and apply are similar. They let you decide which object the this keyword will point to inside the function (that's the important bit!).

apply accepts an array of arguments, call accepts individual arguments.

The closest thing to call() is PHP's call_user_func(). The closest thing to apply() is PHP's call_user_func_array().

JavaScript objects share something with PHP arrays: They are key/value pairs.

// an anonymous function assigned to the key "foo"
var obj = {
  foo: function () { console.log(arguments); }
};

This means you can access object properties either with the dot notation:

// direct function call
obj.foo(1, 2, 3);

Or through square bracket notation (note that object keys are strings):

var funcName = "foo";
obj[funcName](1, 2, 3);
obj[funcName].call(obj, 1, 2, 3);
obj[funcName].apply(obj, [1, 2, 3]);

Square bracket notation gives you the freedom to choose an object property dynamically. If this property happens to be a function, apply() gives you the freedom to choose function arguments dynamically.

Every top-level function that has not been declared as the property of some object will become the property of the global object. In browsers the global object is window. (So the function foo() in my first code block above really is window.foo.)

Note that this does not work like in PHP. It will point to the object the function has been called on, not the object the function "belongs to". (The concept "belongs to" does not really exist in JavaScript. Things can be modeled that way, but it's only a convention.)

With direct calling (obj.foo(1, 2, 3)), this will point to obj. With call and apply, this will point to whatever object you want to. This is a lot more useful than it sounds at first. Most of the time when you want to call functions dynamically, you will end up using apply.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
3

Check out Function.apply:

function test(a, b) { console.log([a, b]) }

test.apply(null, [1, 2]); // => [ 1, 2 ]
Pavel Strakhov
  • 39,123
  • 5
  • 88
  • 127
1

Late to the party, but now with ES6 you can simply do

function FunctionX(a,b,c,d){
    return a + b + c + d;
}

let fx = "FunctionX";
let params = [ 1, 10, 100, 200 ];

let answer = window[fx]( ... params);
let answer2 = globalThis[fx]( ... params );   // this is more cross-platform

to unpack your argument array