This snippet is cut from Secrets of the JavaScript Ninja.
function log() {
try {
console.log.apply( console, arguments );
} catch(e) {
try {
opera.postError.apply( opera, arguments );
} catch(e){
alert( Array.prototype.join.call( arguments, " " ) );
}
}
}
Why should I use apply and what's the difference between console.log.apply(console, arguments) and console.log(arguments)?
In this case, the log function may accept any number of arguments.
Using .apply(), it doesn't matter how many arguments are passed. You can give the set to console.log(), and they will arrive as individual arguments.
So if you do:
console.log(arguments)
...you're actually giving console.log a single Arguments object.
But when you do:
console.log.apply( console, arguments );
...it's as though you passed them separately.
Other useful examples of using .apply() like this can be demonstrated in other methods that can accept a variable number of arguments. One such example is Math.max().
A typical call goes like this:
var max = Math.max( 12,45,78 ); // returns 78
...where it returns the largest number.
What if you actually have an Array of values from which you need the largest? You can use .apply() to pass the collection. Math.max will think they were sent as separate arguments instead of an Array.
var max = Math.max.apply( null, [12,45,92,78,4] ); // returns 92
As you can see, we don't need to know in advance how many arguments will be passed. The Array could have 5 or 50 items. It'll work either way.
If you have
function log() {
console.log.apply(console, arguments);
}
and call it like log('foo'); then that translates to console.log.apply(console, ['foo']); which is equivalent to console.log('foo'); which is what you want.
If you defined it like
function log() {
console.log(arguments);
}
instead then log('foo'); would be equivalent to log(['foo']); which is not what you want.
The apply function changes the value of this in the callee as well as letting you pass an array for the arguments.
For example, if you want to pass an array as arguments to a function:
function foo(value1, value2, value3) {
alert("Value 1 is "+value1+".");
alert("Value 2 is "+value2+".");
alert("Value 3 is "+value3+".");
}
var anArray=[1, 2, 3];
foo(anArray); // This will not work. value1 will be anArray, and value 2 and 3 will be undefined.
foo.apply(this, anArray); // This works, as anArray will be the arguments to foo.
Or, another use: changing this:
function Foo() {
this.name="world";
this.sayHello=function() {
alert("Hello, "+this.name);
};
}
var foo=new Foo();
foo.sayHello(); // This works, as this will be foo in foo's sayHello.
var sayHello=foo.sayHello;
sayHello(); // This does not work, as this will not be foo.
sayHello.apply(foo, []); // This will work, as this will be foo.
Let's discuss some background, why apply exist specially when we have call method with similar syntax.
First we need to understand some topics:
Variadic function:
In computer programming it is a function which can accept any number
of arguments.
Data structure is JavaScript:
In javascript if we are dealing with data than most commonly used data structures is array, and in most cases we get data in the form of array.
Now if we are executing any variadic function in javascript than our call will look like this -
average is a variadic function, and its call will look like,
average(1,2,3);
average(1);
average(3,5,6,7,8);
and if are getting data in array format(in most of the cases we will get data in array format for Variadic functions), than we need to call our function like -
average(array[0],array[1],array[2],array[3],.... and so on)
What if we got an array with length 100 items, do we write it like this ?
No, We have apply method, which was designed just for this purpose.
average.apply(null,array);
console.log(arguments) would send a single argument to console.log, the array of arguments passed to your log method. console.log.apply(console, arguments) sends the possibly multiple arguments as multiple arguments instead of a single array.
Related
I was breaking down some code I found. I got stuck on a specific issue and managed to break it down into a smaller piece. Just keep in mind that this code is part of a much bigger piece of code.
function foo(string, num) {
console.log("calling foo ---", string, num);
}
This following bit looks like it should work. I pass my two arguments into the array that's passed into apply.
But as you can see by the comment I added. The num parameter is undefined. And the string parameter has the second value in the array I passed in, which was intended to be the for num.
foo.bind.apply(foo, ["fdsf", 432])(); // calling foo --- 432 undefined
But if I add an additional value at the beginning of the array (it could be anything), Then everything works smoothly.
foo.bind.apply(foo, [0, "fdsf", 432])(); // calling foo --- fdsf 432
Can anyone please shed some light on what's happening here? If I call bind or apply separately, the array padding is not needed. It only seems to be needed when combining them.
Why is the additional value at the beginning of the array needed?
Also, here is the original code for reference. It's from one of Kyle Simpsons books.
function asyncify(fn) {
var orig_fn = fn,
intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 );
fn = null;
return function() {
if (intv) {
fn = orig_fn.bind.apply(orig_fn, [this].concat([].slice.call(arguments)));
} else {
orig_fn.apply( this, arguments );
}
};
}
There's a lot more going on here. But inside the if statement about 2/3's though the code, you can see it.
The second argument for apply is the array, with the first value (the padding) being the global object (I'm assuming it's the global object, it's not important, it could be anything). followed by any of the arguments that were passed in.
Some of that extra bit is just turning the arguments array-like object into an array.
The first argument that bind accepts is the this value to be used inside the function. So, if you use
fn.bind.apply(fn, arr)
the first item of the arr becomes the first argument of bind, eg, it's equivalent to:
fn.bind(arr[0], arr[1], arr[2] /* ... */ )
So the first item of the array becomes the this value, the second item of the array becomes the first argument, the third item of the array becomes the second argument, etc.
Here's a live example of how "padding" the array with a value at the start becomes the new this value when the function is called:
const obj = { prop: 'val' };
function foo(string, num) {
console.log("calling foo ---", string, num);
console.log('this:', this);
}
const fn = foo.bind.apply(foo, [obj, 'str', 55]);
fn();
As for the repetitive nature of
foo.bind.apply(foo, ...
It's necessary to repeat the function name when calling apply so that when bind is called, it's called with the right calling context - the this value used when .bind is called is how .bind knows which function to bind. (The first argument passed to .apply, just like .call, is the this value to be used in the function being applied)
This snippet is cut from Secrets of the JavaScript Ninja.
function log() {
try {
console.log.apply( console, arguments );
} catch(e) {
try {
opera.postError.apply( opera, arguments );
} catch(e){
alert( Array.prototype.join.call( arguments, " " ) );
}
}
}
Why should I use apply and what's the difference between console.log.apply(console, arguments) and console.log(arguments)?
In this case, the log function may accept any number of arguments.
Using .apply(), it doesn't matter how many arguments are passed. You can give the set to console.log(), and they will arrive as individual arguments.
So if you do:
console.log(arguments)
...you're actually giving console.log a single Arguments object.
But when you do:
console.log.apply( console, arguments );
...it's as though you passed them separately.
Other useful examples of using .apply() like this can be demonstrated in other methods that can accept a variable number of arguments. One such example is Math.max().
A typical call goes like this:
var max = Math.max( 12,45,78 ); // returns 78
...where it returns the largest number.
What if you actually have an Array of values from which you need the largest? You can use .apply() to pass the collection. Math.max will think they were sent as separate arguments instead of an Array.
var max = Math.max.apply( null, [12,45,92,78,4] ); // returns 92
As you can see, we don't need to know in advance how many arguments will be passed. The Array could have 5 or 50 items. It'll work either way.
If you have
function log() {
console.log.apply(console, arguments);
}
and call it like log('foo'); then that translates to console.log.apply(console, ['foo']); which is equivalent to console.log('foo'); which is what you want.
If you defined it like
function log() {
console.log(arguments);
}
instead then log('foo'); would be equivalent to log(['foo']); which is not what you want.
The apply function changes the value of this in the callee as well as letting you pass an array for the arguments.
For example, if you want to pass an array as arguments to a function:
function foo(value1, value2, value3) {
alert("Value 1 is "+value1+".");
alert("Value 2 is "+value2+".");
alert("Value 3 is "+value3+".");
}
var anArray=[1, 2, 3];
foo(anArray); // This will not work. value1 will be anArray, and value 2 and 3 will be undefined.
foo.apply(this, anArray); // This works, as anArray will be the arguments to foo.
Or, another use: changing this:
function Foo() {
this.name="world";
this.sayHello=function() {
alert("Hello, "+this.name);
};
}
var foo=new Foo();
foo.sayHello(); // This works, as this will be foo in foo's sayHello.
var sayHello=foo.sayHello;
sayHello(); // This does not work, as this will not be foo.
sayHello.apply(foo, []); // This will work, as this will be foo.
Let's discuss some background, why apply exist specially when we have call method with similar syntax.
First we need to understand some topics:
Variadic function:
In computer programming it is a function which can accept any number
of arguments.
Data structure is JavaScript:
In javascript if we are dealing with data than most commonly used data structures is array, and in most cases we get data in the form of array.
Now if we are executing any variadic function in javascript than our call will look like this -
average is a variadic function, and its call will look like,
average(1,2,3);
average(1);
average(3,5,6,7,8);
and if are getting data in array format(in most of the cases we will get data in array format for Variadic functions), than we need to call our function like -
average(array[0],array[1],array[2],array[3],.... and so on)
What if we got an array with length 100 items, do we write it like this ?
No, We have apply method, which was designed just for this purpose.
average.apply(null,array);
console.log(arguments) would send a single argument to console.log, the array of arguments passed to your log method. console.log.apply(console, arguments) sends the possibly multiple arguments as multiple arguments instead of a single array.
I'm not quite sure if the title is correct because I'm not sure how to describe my question, but basically I'm wondering how jQuery can handle functions that take things like ("Some String", true, function(){}) and ("Some String", function() {}). IE, it seems like it's an overloaded function, I'd expect the function to be something like
function DoSomething(theStr, theBool, theFunc) {
//...
}
but that doesn't explain how it can handle the 2 argument call, at least to me. Anyone able to explain, or is it just a lot of if/else.
jQuery does type checking or arguments internally, shifting parameters (like a callback, typically at the end) forward if there are no arguments in-between. Let's take for example $.get() where the data argument is optional, the signature looks like this:
jQuery.get(url, [data], [callback(data, textStatus, XMLHttpRequest)], [dataType])
And the check looks like:
if ( jQuery.isFunction( data ) ) {
type = type || callback;
callback = data;
data = null;
}
jQuery.isFuncton() is just a repeatedly used shortcut for jQuery.type(obj) === "function" which is really using Object.toString(), but this used to be done with typeof() directly.
You can see the full source from jQuery 1.4.4 here.
How about just opening the file containing the culprit code?
My guess is that it's either doing type-checking on the variables (using typeof or such), or it's using arguments.length to determine how many arguments were passed, and using that to choose from a list of predefined allowed parameters.
JavaScript does not support method overloading through the use of different method signatures.
You can, in fact, pass 0 arguments to methods that list one or more, or pass more arguments than are listed.
All arguments passed to a method can be accessible through the keyword 'arguments' which behaves like an array.
That being said, you can then check for the presence of arguments directly or via the 'arguments' array.
function ABC(arg1, arg2, arg3){
if(typeof(arg2) === 'undefined') { }
if(typeof(arg3) === 'function'){ }
// arguments[0] == arg1, etc
}
ABC(1) ; // arg2 and arg3 are undefined
ABC(1,2,3,4); // arg1, arg2, and arg3 are defined, plus arguments[3] === 4
Knowing the above, you can therefore figure out how many arguments were provided, and use typeof() calls to determine what type.
In JavaScript, the argument list in a function definition doesn't force you to call the function with exactly those arguments:
// Given this definition....
function foo(one, two){
}
// ... all these calls are valid:
foo();
foo(1);
foo(1, 2);
foo(1, 2, 3);
And, of course, JavaScript is loosely typed as well:
foo(1, 2);
foo("Hello", "World");
foo(new Date, {foo: "bar"});
Using these two concepts, the language allows you to overload methods to your will:
function foo(){
var info = "Argument list:\n";
for(var i=0, len=arguments.length; i<len; i++){
info += "- Argument #" + (i+1) + " is a " + typeof(arguments[i]) + "\n";
}
alert(info);
}
foo(1, "1", {}, [], function(){});
Gives:
Argument list:
- Argument #1 is a number
- Argument #2 is a string
- Argument #3 is a object
- Argument #4 is a object
- Argument #5 is a function
+1 for Jani's answer. Just check the type of each parameter, and adjust your logic accordingly.
i'm reading through jQuery's "Plugins/Authoring" though i already wrote a few jQuery-Plugins. Now I see that jQuery has a special way of scoping the methods and calling:
(function( $ ){
var methods = {
init : function( options ) { // THIS },
show : function( ) { // IS },
hide : function( ) { // GOOD },
update : function( content ) { // !!! }
};
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
I understand the concept of what will happen in the end… but how exactly? This part is what confuses me:
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
}
Why Array.prototype.slide.call(argumetns, 1)? And where does the variable "arguments" come from all of the sudden? Any brief or deeper explanation is much appreciated. It is said, that this is how plugins should be written… so i'd like to know why.
Thanks!
arguments
arguments is a part of the JavaScript language. I was confused in exactly the way you were when I first ran into it; it's not just you. :-) It's an automatic local variable in every function, and is an array-like structure giving you all of the arguments (see Section 10.6 of the spec), e.g.:
function foo() {
var index;
for (index = 0; index < arguments.length; ++index) {
alert(arguments[index]);
}
}
foo("one", "two"); // alerts "one", then alerts "two"
When I say arguments is array-like, I mean it — it's not an Array. Its references to the arguments are live (and bidirectional). For instance:
function foo(namedArg, anotherNamedArg) {
alert(namedArg === arguments[0]); // alerts true, of course
alert(anotherNamedArg === arguments[1]); // also alerts true
namedArg = "foo";
alert(arguments[0]); // alerts "foo"
arguments[0] = "bar";
alert(namedArg); // alerts "bar"
}
Note that when assigning a value to namedArg, the result is reflected in arguments[0], and vice-versa.
arguments is really cool, but only use it if you need to — some implementations speed up calling functions by not hooking it up until/unless the function actually first tries to access it, which can slow the function down (very slightly).
arguments also has property on it called callee, which is a reference to the function itself:
function foo() {
alert(foo === arguments.callee); // alerts true
}
However, it's best to avoid using arguments.callee for several reasons. One reason is that in many implementations, it's really slow (I don't know why, but to give you an idea, the function call overhead can increase by an order of magnitude if you use arguments.callee). Another reason is that you can't use it in the new "strict" mode of ECMAScript5.
(Some implementations also had arguments.caller — shudder — but fortunately it was never widespread and is not standardized anywhere [nor likely to be].)
The slice call and apply
Regarding
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
What that's doing is using the Array#slice method to copy the arguments into an array (minus the first argument, which was the method to call), and then passing the resulting array into the Function#apply function on the function instance it's calling. Function#apply calls the function instance with the given this object and the arguments supplied as an array. The code's not just using arguments.slice because (again) arguments isn't really an Array and so you can't rely on it having all of the Array functions, but the specification specifically says (in Section 15.4.4.10) that you can apply the Array.prototype.slice function to anything that's array-like, and so that's what they're doing.
Function#apply and Function#call are also built-in parts of JavaScript (see Sections 15.3.4.3 and 15.3.4.4). Here are simpler examples of each:
// A function to test with
function foo(msg, suffix) {
alert(this.prefix + ": " + msg + suffix);
}
// Calling the function without any `this` value will default `this`
// to the global object (`window` on web browsers)
foo("Hi there", "!"); // Probably alerts "undefined: Hi there!" because the
// global object probably doesn't have a `prefix` property
// An object to use as `this`
var obj = {
prefix: "Test"
};
// Calling `foo` with `this` = `obj`, using `call` which accepts the arguments
// to give `foo` as discrete arguments to `call`
foo.call(obj, "Hi there", "!"); // alerts "Test: Hi there!"
// ^----^-----------^---- Three discrete args, the first is for `this`,
// the rest are the args to give `foo`
// Calling `foo` with `this` = `obj`, using `apply` which accepts the arguments
// to give `foo` as an array
foo.apply(obj, ["Hi there", "!"]); // alerts "Test: Hi there!"
// ^---------------^---- Note that these are in an array, `apply`
// takes exactly two args (`this` and the
// args array to use)
arguments is a keyword, the arguments passed to the function. But you don't want all of them, since the first you know, it's method, so this is taking every argument past the first to use in .apply()...passing those arguments into whichever method was specified in the first method argument.
If it can't find method (meaning the first argument wasn't 'init', 'show', 'hide', or 'update', then it goes to the else portion, and passes all the arguments to the init method (the default, if you will).
For example:
.tooltip({ thing: value }) would call init({ thing: value }) since that's the default
.tooltip('show', var1, var2) would call show(var1, var2)
Variable arguments is defined javascript variable. It stores all arguments called in function in array. For example :
function variablesCount() {
alert(arguments.length);
}
variablesCount(var1, var2); // alerts 2
Array.prototype.slide.call(argumetns, 1) converts the arguments pseudo-array into a real array (skipping the first element).
arguments contains all arguments the function you are currently in has been called with. It is automatically provided by JavaScript. Since we need a real array (arguments cannot be manipulated, etc.), we convert it to one using this statement.
arguments is a special JavaScript variable which means "all the arguments given to this function". It isn't quite an array, but behaves almost like one, so instead of saying arguments.slice(1) it calls Array.prototype.slice.call(arguments, 1); what it's doing it taking all except the first value in the list - for func(1, 2, 3, 4) it will be [2, 3, 4].
The end result of this is that when you call $.fn.tooltip('foo', 'bar', 'baz') it will try to call methods['foo']('bar', 'baz').
Is there a way to allow "unlimited" vars for a function in JavaScript?
Example:
load(var1, var2, var3, var4, var5, etc...)
load(var1)
Sure, just use the arguments object.
function foo() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
In (most) recent browsers, you can accept variable number of arguments with this syntax:
function my_log(...args) {
// args is an Array
console.log(args);
// You can pass this array as parameters to another function
console.log(...args);
}
Here's a small example:
function foo(x, ...args) {
console.log(x, args, ...args, arguments);
}
foo('a', 'b', 'c', z='d')
=>
a
Array(3) [ "b", "c", "d" ]
b c d
Arguments
0: "a"
1: "b"
2: "c"
3: "d"
length: 4
Documentation and more examples here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
Another option is to pass in your arguments in a context object.
function load(context)
{
// do whatever with context.name, context.address, etc
}
and use it like this
load({name:'Ken',address:'secret',unused:true})
This has the advantage that you can add as many named arguments as you want, and the function can use them (or not) as it sees fit.
I agree with Ken's answer as being the most dynamic and I like to take it a step further. If it's a function that you call multiple times with different arguments - I use Ken's design but then add default values:
function load(context) {
var defaults = {
parameter1: defaultValue1,
parameter2: defaultValue2,
...
};
var context = extend(defaults, context);
// do stuff
}
This way, if you have many parameters but don't necessarily need to set them with each call to the function, you can simply specify the non-defaults. For the extend method, you can use jQuery's extend method ($.extend()), craft your own or use the following:
function extend() {
for (var i = 1; i < arguments.length; i++)
for (var key in arguments[i])
if (arguments[i].hasOwnProperty(key))
arguments[0][key] = arguments[i][key];
return arguments[0];
}
This will merge the context object with the defaults and fill in any undefined values in your object with the defaults.
It is preferable to use rest parameter syntax as Ramast pointed out.
function (a, b, ...args) {}
I just want to add some nice property of the ...args argument
It is an array, and not an object like arguments. This allows you to apply functions like map or sort directly.
It does not include all parameters but only the one passed from it on. E.g. function (a, b, ...args) in this case args contains
argument 3 to arguments.length
Yes, just like this :
function load()
{
var var0 = arguments[0];
var var1 = arguments[1];
}
load(1,2);
As mentioned already, you can use the arguments object to retrieve a variable number of function parameters.
If you want to call another function with the same arguments, use apply. You can even add or remove arguments by converting arguments to an array. For example, this function inserts some text before logging to console:
log() {
let args = Array.prototype.slice.call(arguments);
args = ['MyObjectName', this.id_].concat(args);
console.log.apply(console, args);
}
Although I generally agree that the named arguments approach is useful and flexible (unless you care about the order, in which case arguments is easiest), I do have concerns about the cost of the mbeasley approach (using defaults and extends). This is an extreme amount of cost to take for pulling default values. First, the defaults are defined inside the function, so they are repopulated on every call. Second, you can easily read out the named values and set the defaults at the same time using ||. There is no need to create and merge yet another new object to get this information.
function load(context) {
var parameter1 = context.parameter1 || defaultValue1,
parameter2 = context.parameter2 || defaultValue2;
// do stuff
}
This is roughly the same amount of code (maybe slightly more), but should be a fraction of the runtime cost.
While #roufamatic did show use of the arguments keyword and #Ken showed a great example of an object for usage I feel neither truly addressed what is going on in this instance and may confuse future readers or instill a bad practice as not explicitly stating a function/method is intended to take a variable amount of arguments/parameters.
function varyArg () {
return arguments[0] + arguments[1];
}
When another developer is looking through your code is it very easy to assume this function does not take parameters. Especially if that developer is not privy to the arguments keyword. Because of this it is a good idea to follow a style guideline and be consistent. I will be using Google's for all examples.
Let's explicitly state the same function has variable parameters:
function varyArg (var_args) {
return arguments[0] + arguments[1];
}
Object parameter VS var_args
There may be times when an object is needed as it is the only approved and considered best practice method of an data map. Associative arrays are frowned upon and discouraged.
SIDENOTE: The arguments keyword actually returns back an object using numbers as the key. The prototypal inheritance is also the object family. See end of answer for proper array usage in JS
In this case we can explicitly state this also. Note: this naming convention is not provided by Google but is an example of explicit declaration of a param's type. This is important if you are looking to create a more strict typed pattern in your code.
function varyArg (args_obj) {
return args_obj.name+" "+args_obj.weight;
}
varyArg({name: "Brian", weight: 150});
Which one to choose?
This depends on your function's and program's needs. If for instance you are simply looking to return a value base on an iterative process across all arguments passed then most certainly stick with the arguments keyword. If you need definition to your arguments and mapping of the data then the object method is the way to go. Let's look at two examples and then we're done!
Arguments usage
function sumOfAll (var_args) {
return arguments.reduce(function(a, b) {
return a + b;
}, 0);
}
sumOfAll(1,2,3); // returns 6
Object usage
function myObjArgs(args_obj) {
// MAKE SURE ARGUMENT IS AN OBJECT OR ELSE RETURN
if (typeof args_obj !== "object") {
return "Arguments passed must be in object form!";
}
return "Hello "+args_obj.name+" I see you're "+args_obj.age+" years old.";
}
myObjArgs({name: "Brian", age: 31}); // returns 'Hello Brian I see you're 31 years old
Accessing an array instead of an object ("...args" The rest parameter)
As mentioned up top of the answer the arguments keyword actually returns an object. Because of this any method you want to use for an array will have to be called. An example of this:
Array.prototype.map.call(arguments, function (val, idx, arr) {});
To avoid this use the rest parameter:
function varyArgArr (...var_args) {
return var_args.sort();
}
varyArgArr(5,1,3); // returns 1, 3, 5
Use the arguments object when inside the function to have access to all arguments passed in.
Be aware that passing an Object with named properties as Ken suggested adds the cost of allocating and releasing the temporary object to every call. Passing normal arguments by value or reference will generally be the most efficient. For many applications though the performance is not critical but for some it can be.
Use array and then you can use how many parameters you need. For example, calculate the average of the number elements of an array:
function fncAverage(sample) {
var lenghtSample = sample.length;
var elementsSum = 0;
for (var i = 0; i < lenghtSample; i++) {
elementsSum = Number(elementsSum) + Number(sample[i]);
}
average = elementsSum / lenghtSample
return (average);
}
console.log(fncAverage([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // results 5.5
let mySample = [10, 20, 30, 40];
console.log(fncAverage(mySample)); // results 25
//try your own arrays of numbers