0

I have an object:

var obj = {
    foo: 'foo',
    bar: 'bar'
};

And I have a function:

var f = function(bar, foo) {
    // Do something with bar and foo
};

Is there a way I can dynamically call the function using the object, so that obj['foo'] is passed as the 'foo' parameter, and obj['bar'] is passed as the 'bar' parameter? Note that I may not know the actual names of the keys and values or the function argument names, the ones provided are just an example, so f.call(this, obj['bar'], obj['foo']); won't do.

driima
  • 623
  • 1
  • 11
  • 28
  • You would need to inspect the argument names of the function, create an array of the arguments in the right order, and then `f.apply(null, args)`… Maybe someone can provide an implementation from here… – deceze Aug 31 '16 at 10:34
  • You want to pass object property names based on functions arguments names? Thats not a good idea. – Maxx Aug 31 '16 at 10:39
  • Not able to understand what you want. elaborate – ajaykumar Aug 31 '16 at 10:40
  • 1
    @ajay OP has an object with keys that correspond to a function's parameter names. OP wants to call the function, passing the appropriate values from the object to the appropriate parameters of the function. – deceze Aug 31 '16 at 10:41
  • 1
    try it http://stackoverflow.com/questions/6921588/is-it-possible-to-reflect-the-arguments-of-a-javascript-function – Maxx Aug 31 '16 at 10:44

5 Answers5

2

Sure, in ES6

var f = function({bar, foo}) {
    console.log('foo', foo, 'bar', bar)
};

var obj = {
    foo: '11',
    bar: '22'
};


f(obj)
georg
  • 211,518
  • 52
  • 313
  • 390
1

Based on deceze's comment, I managed to come up with a solution. I'm not exactly sure how fast it is, but it gets the job done. Improvements are welcome! Thanks for everyone's help.

I used this function from this answer:

function getParameterNames(fn){
    var fstr = fn.toString();
    return fstr.match(/\(.*?\)/)[0].replace(/[()]/gi,'').replace(/\s/gi,'').split(',');
}

And made the following:

var obj = {
    foo: 'foo',
    bar: 'bar'
};

var f = function(bar, foo) {
    // Do something with bar and foo
};

var sortedArgs = [];
getParameterNames(f).forEach(function(item) {
    sortedArgs.push(args[item]);
});

f.apply(this, sortedArgs);
Community
  • 1
  • 1
driima
  • 623
  • 1
  • 11
  • 28
  • 1
    What happens if your source gets transpiled and/or minified? – georg Aug 31 '16 at 11:12
  • To note: what you're doing is pretty much how Angular's injection works, and to deal with minification it has an explicit annotation syntax in which you need to list out the parameters as strings *too*. So, yes, this is a real concern and this is not generally applicable… – deceze Aug 31 '16 at 11:26
  • Understandable. I actually have a variety of ways the method can be called with specific arguments, and this (using an object) was one of the ways. The other ways call the method with arguments based on the order they should be passed. The primary purpose of this is for it to be used for in-house development, so it wouldn't be minified. – driima Aug 31 '16 at 12:29
0

You could check if provided argument is object and then use destructuring

var obj = {
  foo: 'foo',
  bar: 'bar'
};

var f = function(foo, bar) {
  if(typeof arguments[0] == 'object') var {foo, bar} = arguments[0];
  console.log('Foo: ' + foo);
  console.log('Bar: ' + bar)
};

f(obj);
f('lorem', 'ipsum');
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
0

You could bind the object to the function.

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

var obj = {
        foo: 'foo',
        bar: 'bar'
    },
    f = function(k) {
        console.log(this[k]);
    }.bind(obj);

f('foo');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You can write a function which will wrap your function and use new function instead to get what you want.

function wrapper(fn) {
  var fnString = fn.toString();
  var fnRegex = /\((.*)\)/;

  var params = fnString
    .match(fnRegex)[1]
    .replace(/\s/, '')
    .split(',')
    .filter(function(arg) {
      return arg;
    });

  return function (o) {
    var args = params
      .map(function(arg) {
        return o[arg];
      });
    return fn.apply(null, args);
  }
}

// Example: your function.
function yourFunc(foo, bar) {
  console.log('my foo: ' + foo);
  console.log('my bar: ' + bar);
}

// wrap your function and use new function instead.
var enhancedFunc = wrapper(yourFunc);

enhancedFunc({foo: 1, bar: 2});
Caojs
  • 175
  • 7
  • Can you describe how the wrapper works and how it passes the parameters to the function based on the key names, in the correct order? – driima Aug 31 '16 at 12:32
  • First, it parses `fn.toString()` to get an array of params in the correct order. In the example, `fn.toString()` => `'function yourFunc(foo, bar) {...}'` => `'foo,bar'` => `['foo', 'bar']`. After that, it return a new function, the new function uses above array to get array of values and call `fn` with that array, `['foo', 'bar']` (map with `o`)=> `[1, 2]` => `fn.apply(null, [1, 2])`. – Caojs Aug 31 '16 at 13:41