Kirk Ouimet
Kirk Ouimet

Reputation: 28364

Invoke a Function with the Correct Parameters from an Object in JavaScript

Let's say I have an object that looks like this:

{
     'apple': 'nice',
     'banana': 'decent',
     'cherry': 'yuck',
}

and I have these two methods:

function eatItems(cherry, apple) { }
function throwItem(banana) { }

My two questions:

  1. Is it possible for me to invoke eatItem and send the arguments in the correct order? Maybe something like:

    eatItems.call(this, {'cherry': cherry, 'apple': apple});

  2. What if I don't know what arguments eatItems receives, can I dynamically look up the names of the arguments for a function so I can know the order that I need to throw them in?

Upvotes: 0

Views: 57

Answers (2)

MaxArt
MaxArt

Reputation: 22627

There's a way, indeed, and it involves calling toString on a function:

var source = eatItems.toString();
// => "function eatItems(cherry, apple) { }"

The next step is to parse the string you've got to get the names of the arguments:

var args = source.substring(source.indexOf("(") + 1, source.indexOf(")")),
    argNames = /\S/.test(args) ? args.split(/\s*,\s*/) : [];

A few caveats:

  1. This solution has been kept quite simple. It doesn't handle comments in the function definition.
  2. Not every browser can correctly convert a function to a string (the PS3 browser comes to my mind), but they're a really small minority anyway.
  3. I haven't tested it, but there may be some performance issues on slower machines and/or older browsers with large functions.

And, overall, this solution is more like an exercise. I wouldn't recommend taking this pattern in Javascript. Don't forget that some functions handle a variable number of arguments, and you won't find them listed in their definition. Rethink your code, and find a better way.

Upvotes: 2

elclanrs
elclanrs

Reputation: 94101

If I understand correctly you want extract the argument names from the function, and inject data from an object based on those names. This can be accomplished by converting the function to a string, extracting the arguments, and applying the function with those arguments:

function inject(data, f) {
  var args = f.toString()
    .match(/function\s*?\((.+?)\)/)
    .pop()
    .split(',')
    .map(function(a){return data[a.trim()]})
  return function() {
    return f.apply(this, args)
  }
}

var data = {
  apple: 'nice',
  banana: 'decent',
  cherry: 'yuck',
}

var eat = inject(data, function(cherry, apple) {
  console.log(cherry, apple)
})

eat() //=> yuck, nice

The obvious problem with this approach is that it is highly dependent on the variable names, so when you minify your code, the variables will get mangled and the function will stop working. This is a known problem in AngularJS, which uses something similar for their dependency injection.

This is often an XY problem, or an anti-pattern at the very least.

Upvotes: 1

Related Questions