Can I call a function with array of arguments in a convenient way in JavaScript?
Example:
var fn = function() {
console.log(arguments);
}
var args = [1,2,3];
fn(args);
I need arguments to be [1,2,3], just like my array.
Since the introduction of ES6, you can sue the spread syntax in the function call:
const args = [1,2,3];
fn(...args);
function fn() {
console.log(arguments);
}
Before ES6, you needed to use apply.
var args = [1,2,3];
fn.apply(null, args);
function fn() {
console.log(arguments);
}
Both will produce the equivalent function call:
fn(1,2,3);
Notice that I used null as the first argument of the apply example, which will set the this keyword to the global object (window) inside fn or undefined under strict mode.
Also, you should know that the arguments object is not an array, it's an array-like object, that contains numeric indexes corresponding to the arguments that were used to call your function, a length property that gives you the number of arguments used.
In ES6, if you want to access a variable number of arguments as an array, you can also use the rest syntax in the function parameter list:
function fn(...args) {
args.forEach(arg => console.log(arg))
}
fn(1,2,3)
Before ES6, if you wanted to make an array from your arguments object, you commonly used the Array.prototype.slice method.
function fn() {
var args = Array.prototype.slice.call(arguments);
console.log(args);
}
fn(1,2,3);
Edit: In response to your comment, yes, you could use the shift method and set its returned value as the context (the this keyword) on your function:
fn.apply(args.shift(), args);
But remember that shift will remove the first element from the original array, and your function will be called without that first argument.
If you still need to call your function with all your other arguments, you can:
fn.apply(args[0], args);
And if you don't want to change the context, you could extract the first argument inside your function:
function fn(firstArg, ...args) {
console.log(args, firstArg);
}
fn(1, 2, 3, 4)
In ES5, that would be a little more verbose.
function fn() {
var args = Array.prototype.slice.call(arguments),
firstArg = args.shift();
console.log(args, firstArg);
}
fn(1, 2, 3, 4);
In ECMAScript 6, you can use spread syntax (...) for that purpose. It's way simpler and easier to understand than Function.prototype.apply().
Code example:
const fn = function() {
console.log(arguments);
}
const args = [1,2,3];
fn(...args);
Related
I have an object of arbitrary functions and a function that takes a method name and an object or an array with parameters and then selects and calls the function accordingly. The problem is that functions have a variable number of inputs and some of them contain optional fields with default values and I can't find a general way to map inputs of the parameters to inputs of the function.
One way to solve the problem when the arguments come in the form of the array is just simply called the function with the ... operator: func(...args), but that still leaves me with a problem, because I can't use these methods on objects. Is there any way to map object values to function inputs by using object keys?
Abstract example of the situation:
const funcs = {
func1: (arg1, arg2, arg3 = 'something') => .....does something
func2: () => ....does something
func3: (anotherArg1) => ...does something
}
function callFunction(method: string, args: unknown[]| object) {
if (Array.isArray(args)) {
return funcs[method](...args)
}
else (if args instanceof Object) {
//... Here I need to parse the args and call the function in "funcs" object.
}
}
Just spread the second argument of callFunction(method, ...args)
const funcs = {
func1: (arg1, arg2, arg3 = 'something') => {
[arg1, arg2, arg3].forEach((a, i) => console.log(`arg${i+1}:`, JSON.stringify(a)));
}
}
function callFunction(method, ...args) {
return funcs[method](...args)
}
const method = 'func1';
callFunction(method, [1, 2], {foo: 'bar'})
So I get that an array of [200,599] is returned from the promise and the callback function inside spread is being passed into Function.apply.bind, but now I'm lost. How is the array of [200,599] split into x and y? How exactly does the apply.bind work?
function getY(x) {
return new Promise( function(resolve,reject){
setTimeout( function(){
resolve( (3 * x) - 1 );
}, 100 );
} );
}
function foo(bar,baz) {
var x = bar * baz;
// return both promises
return [
Promise.resolve( x ),
getY( x )
];
}
function spread(fn) {
return Function.apply.bind( fn, null );
}
Promise.all(
foo( 10, 20 )
)
.then(
spread( function(x,y){
console.log( x, y ); // 200 599
} )
)
.apply() is a method on function objects. Like so:
console.log(Math.max.apply(null, [1, 2, 3])); // 3
.apply() accepts two arguments, the context (what would become this inside of the target function) and an iterable of arguments (usually an array, but the arguments array like also works).
.bind() is a method on function objects. Like so:
const x = {
foo: function() {
console.log(this.x);
},
x: 42
}
var myFoo = x.foo.bind({x: 5});
x.foo(); // 42
myFoo(); // 5
.bind() takes a context (what would become this), and optionally, additional arguments, and returns a new function, with the context bound, and the additional arguments locked
Since .apply() is a function in on itself, it can be bound with .bind(), like so:
Function.prototype.apply.bind(fn, null);
Meaning that the this of .apply() would be fn and the first argument to .apply() would be null. Meaning that it would look like this:
fn.apply(null, args)
Which would spread the parameters from an array.
Spread takes a function and binds the apply method to, partially applying the null argument. So in short,
spread(fn)
is transformed to
args => fn.apply(null, args)
which is the same as using the ES6 spread syntax
args => fn(...args)
where the function got its name from.
If you want the long answer, remember what bind does:
method.bind(context, ...args1)
returns a function that works like
(...args2) => method.call(context, ...args1, ...args2)
In our case, method is apply, the context is fn and the first arguments are null, so the call
Function.apply.bind( fn, null )
will create a function that works like
(...args2) => (Function.apply).call(fn, null, ...args2)
which is equivalent to the fn.apply(…) call above, given that apply is the method inherited from Function.prototype in both accesses.
The spread function is just a utility function to convert an array, into parameters passed to a function. The apply is doing the converting, and the bind is binding it to the calling function so that the "this" context is connected to same function.
To see how spread is working in a simpler form ->
spread(function (x,y){console.log(x,y);})([1,2])
You will get the answer, 1 2, as 1 is passed to x, and 2 is passed to y.
So in your example your promise.all is returning an array of resolved promises.
These are then getting mapped to parameters to your function(x,y).
The reason it works is the "destructuring" nature of apply (if given an array of values, they would be provided spreaded to the function you use apply on).
Now back to your code when calling bind on apply, the value returned is a function which returns the same function provided to bind, the only thing different is when executed it would be called using apply (with an array as thisArg in your case), but it isn't going to be executed until you call it.
In your case when the promise has resolved, the function provided tothen woule be executed with an array of arguments provided by Promise resolution.
function spread(fn){
let boundedFn = fn.bind(fn)
return function(arg){
return boundedFn.apply(null,arg)
}
}
spread((x, y, c) => console.log(x, y, c))([1,2,3])
// or
Promise.resolve([6, 5]).then(spread((a, b) => console.log(a, b)))
The reason bind is provided (in your code) with null as second param is to prevent the array provided by the caller from being given to apply as its first param, which reserved for this.
I'm having an issue with how my arguments are being passed to a new recursion level. Here's a simplified version of what I'm doing:
var newFunction = function(obj) {
var result = "";
var args = [];
Array.prototype.push.apply(args, arguments);
var thisArg = args.shift()
//do stuff to add onto result with thisArg. This block works, so I'm skipping.
if (args.length !== 0) {
result += newFunction(args);
};
return result;
};
The issue I'm having is related to how 'args' is getting passed into newFunction to cycle back through. When the recursive callback is made, args is passed into the new function scope as a single array argument:
original arguments = ("string", true, 9, "string 2")
new arguments in recursion = ([true, 9, string 2])
It NEEDS to be:
original arguments = ("string", true, 9, "string 2")
new arguments in recursion = (true, 9, "string 2")
I'm pretty sure it's related to how I'm using .apply for the args variable. I'm just not sure how to get around that, since you can't .shift() the 'arguments' object. The way I'm doing it is setting args to be an array; so when it gets passed in, it is passing it as a single array. This is the problem with my code...
Do you see what I'm doing wrong? Thanks in advance.
You can use .apply():
result += newFunction.apply(undefined, args);
The .apply() function is like .call, but it expands the array elements out so that (effectively) the array is copied, element by element, into the arguments object in the newly-called function.
In ECMAScript 5, using apply:
var newFunction = function(thisArg) {
var result = "",
args = [].slice.call(arguments, 1);
// ...
if (args.length) result += newFunction.apply(void 0, args);
return result;
};
In ECMAScript 6, using rest parameters and the spread operator,
var newFunction = function(thisArg, ...args) {
var result = "";
// ...
if (args.length) result += newFunction(...args);
return result;
};
I would like to make a generic function wrapper that (for example) prints the called function and its arguments.
Doing so is easy through the arguments quasi-array and simple calls. For example:
function wrap(target, method) {
return function() {
console.log(Array.prototype.slice.call(arguments).join(', '));
return method.apply(target, arguments);
}
}
However, this way of doing of course completely loses the arity of the called function (if you didn't know, one can obtain the arity (number of arguments) of a JavaScript function through its length property).
Is there any way to dynamically create a wrapper function that would copy the arguments of the wrapped function to itself?
I've thought about creating a new Function object, but I don't see any way to statically extract the arguments list, since the arguments property is deprecated.
Here's a solution using Function:
// could also generate arg0, arg1, arg2, ... or use the same name for each arg
var argNames = 'abcdefghijklmnopqrstuvwxyz';
var makeArgs = function(n) { return [].slice.call(argNames, 0, n).join(','); };
function wrap(target, method) {
// We can't have a closure, so we shove all our data in one object
var data = {
method: method,
target: target
}
// Build our function with the generated arg list, using `this.`
// to access "closures"
f = new Function(makeArgs(method.length),
"console.log(Array.prototype.slice.call(arguments).join(', '));" +
"return this.method.apply(this.target, arguments);"
);
// and bind `this` to refer to `data` within the function
return f.bind(data);
}
EDIT:
Here's a more abstract solution, which fixes the closure problem:
function giveArity(f, n) {
return new Function(makeArgs(n),
"return this.apply(null, arguments);"
).bind(f);
}
And a better one, that preserves context when invoked:
function giveArity(f, n) {
return eval('(function('+makeArgs(n)+') { return f.apply(this, arguments); })')
}
Used as:
function wrap(target, method) {
return giveArity(function() {
console.log(Array.prototype.slice.call(arguments).join(', '));
return method.apply(target, arguments);
}, method.length)
}
In Jeremy Ashkenas's awesome Underscore.js library, I tried to understand one thing about the source file. I do not understand this:
var slice = Array.prototype.slice;
args = slice.call(arguments, 2);
So that:
args = Array.prototype.slice.call(arguments, 2);
.call or .apply are the methods of the functions. But here, which functions do .call refer to? The first parameter should be the context, but arguments is context? The second parameter should be the params to pass in the functions. Here they are number 2. What does this mean? Sometimes in the library, it uses 1 or 0. Are they the number of the params to pass in the functions?
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
Question 2:
I do not quite understand the logic of this function. Need help to understand. An example should be very helpful.
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (method.call ? method || value : value[method]).apply(value, args);
});
};
Thank you for help.
The "slice" function on the Array prototype expects that this will refer to the array on which it's supposed to operate. In other words, if you have a real array:
var myArray = [1, 2, 3];
and you call slice():
var sliced = myArray.slice(1);
Then in that call to slice(), this refers to the array "myArray". As Raynos notes in a comment:
myArray.slice(1)
is the same as
myArray.slice.call(myArray, 1);
Thus when you use call() to invoke the function, and pass it arguments as the context object, the slice() code operates on arguments. The other parameters passed via .call() are simply the parameter or parameters for slice() itself. In my example above, note that I passed 1 to the function.
Now as to your second question, that .invoke() function first isolates the arguments passed in after the first two. That means that when you use _.invoke() you pass it two or more arguments: the first is the list to operate on, the second is the method, and the (optional) subsequent arguments are passed to the method for each element of the list.
That call to _.map() is complicated (and in fact I think it's got a little nonsense in it). What it's doing is iterating over the list, calling a function for each value in the list. What that function does to first determine whether the "method" parameter really is a function. If it is, then it calls that function via .apply() with the element of the list as the context. If "method" is not a function, then it assumes it's the name of a property of each list element, and that the properties are functions.
So for example, with a simple list it's pretty simple:
var myList = [1, 2, 3];
var result = _.invoke(myList, function(n) { return this * n; }, 2);
That will give the result [2, 4, 6] because the function I passed multiplies its context object (this) by the parameter passed, and I passed 2 in the call to _.invoke().
With a more complicated list, I can use the second flavor of _.invoke() and call a method on each object in the list:
var getName = function(prefix) { return prefix + " " + this.name; };
var list = [
{ name: "Bob", getName: getName },
{ name: "Sam", getName: getName },
{ name: "Lou", getName: getName }
];
var result = _.invoke(list, "getName", "Congressman");
That will call the "getName" function on each object in the list and return a list made from the results. The effect will be the list ["Congressman Bob", "Congressman Sam", "Congressman Lou"].
Now about that nonsense. In the code for _.invoke():
return _.map(obj, function(value) {
return (method.call ? method || value : value[method]).apply(value, args);
});
That subexpresion method || value will always return the value of "method", or at least almost always barring some exotic trick. If method.call is truthy, then a reference to method must also be truthy. Also, if it were my code, I'd inspect method outside the _.map() callback so that the decision doesn't have to be made over and over again. Maybe something like:
return _.map(obj, method.call ?
function(value) { method.apply(value, args); } :
function(value) { value[method].apply(value, args); }
);