2

I'm trying to have a function that is passed a function and calls it with a set of args. For example:

function foo(func, args) {
  return func(args);
}

However, I don't know how many elements might be in args, and func should be able to be an arbitrary function that takes any number of args. How do I deal with this?

Also say I wanted to store the functions passed to foo and only call them if they hadn't been called before.

Can I just do something like:

var calledFuncs = [];
function foo(func, args) {
  if(calledFuncs.indexOf(func) === -1) {
    calledFuncs.push(func);
    return func(args);
  }
}

Thanks, I'm a bit new to functional programming in JavaScript.

2
  • 2
    If there will be a lot of calledFuncs, then another (faster) solution to prevent recalling would be to put a property directly on the function as a flag. Since functions are Objects, you can add properties just like any other object. Of course the property will be accessible to any code that can access the function, so keep that in mind. Commented Jun 8, 2012 at 17:41
  • If it's for understanding the language and not for compatibility, you can also use a WeakMap of function/called pairs. Commented Jun 8, 2012 at 18:43

3 Answers 3

8

You're looking for func.apply:

  • the first argument is the context aka this value. You don't want to change this, so pass it through.
  • the second argument is an array containing the arguments, which can of course be of dynamic length.

So you could do this:

return func.apply(this, args);

You seem to have a fine approach to your second issue (with the func(args) replaced). You may want to store the return value of called functions, though, since foo now returns undefined for any calls except the first.

Sign up to request clarification or add additional context in comments.

Comments

2

Use apply:

func.apply(this, args);

apply takes an array of arguments as the second argument.

The first argument to apply will be the value of this in the scope of the function that you are calling. So you can pass in the current this or anything else that you want.

As far as your second question goes, that will only work for named functions. You can use func.name to store the function name in your array:

var calledFuncs = [];
function foo(func, args) {
   if(calledFuncs.indexOf(func.name) === -1) {
      calledFuncs.push(func.name);
      return func(args);
   }
}

This won't work for anonymous functions and it doesn't work for named functions in IE or Opera. You're going to have to parse it out, perhaps like so:

var name = func.toString().replace(/\n/g, "").replace(/\s*\(.*$/, "").replace(/^\s*function\s+/, "");

As far as your second question goes, you can do what you're doing right now. But I don't think it would work for the following case:

foo(function() {
}, []);

foo(function() {
}, []);

It will call both of those functions.

15 Comments

I'm unsure what you mean with the named functions requirement. indexOf can search for functions, whereas distinct functions can have the same name.
It won't matter if the function is named or not. It'll be testing for unique object instances. Using .name would be dangerous, as two functions can have the same name.
var a = [function(){},function(){}]; a.indexOf(a[1]) === 1;
@VivinPaliath it's impossible to statically determine if two functions are functionally equivalent, more so with just looking at the .name. There is infinite amount of ways to write a functionally equivalent function of some other function. The only sensible thing to do is to compare references.
@Vivin Paliath: Be careful with that though; even identical source code does not mean identical functionality. In the end it depends on its variable scope etc.
|
1

You want Function.prototype.apply:

func.apply(this,args);

Set the context (the first argument) to whatever you want, including null to get the window global (as you would get with your current func(...) invocation.

Although not directly related to your question, see also the related Function.prototype.call method that allows you to similarly set the context, but pass explicit parameters.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.