- Accessing the unnamed arguments
- The guts of
arguments
- Using
arguments
asArray
- Making a real
Array
- Default values
- Keyword arguments
- Special
arguments
properties
In JavaScript, a function may be called with any number of arguments, no matter how many of them are listed.
For instance:
function go(a,b) { alert("a="+a+", b="+b) } go(1) // a=1, b=undefined go(1,2) // a=1, b=2 go(1,2,3) // a=1, b=2, extra argument is not listed
Arguments which are not provided become undefined
. So we can see if the function is called with no arguments:
function check(x) { alert(x === undefined) // ok now I know there is no x } check()
In some languages, a programmer may write two functions with same name but different parameter list, and the interpreter/compiler would choose the right one:
function log(a) { ... } function log(a,b,c) { ... } log(a) // first function is called log(a,b,c) // second function is called
That is called function polymorphism. In JavaScript, there’s no such thing.
There can be only one function named log
, which is called for any given arguments.
Accessing the unnamed arguments
How do we get more arguments than listed in parameters?
There is a special pseudo-array inside each function called arguments.
It contains all parameters by their number: arguments[0]
, arguments[1]
etc.
Here is an example of how to list all arguments, no matter how many of them:
function sayHi() { for(var i=0; i<arguments.length; i++) { alert("Hi, " + arguments[i]) } } sayHi("Cat", "Alice") // 'Hi, Cat', then 'Hi, Alice'
All parameters are in arguments
, even if the function names some of them: sayHi(a,b,c)
.
The guts of arguments
A frequent beginner mistake is to use Array
methods on it. Shortly, you can’t:
function sayHi() { var a = arguments.shift() // error! arguments is not Array alert(a) } sayHi()
If it isn’t Array
, then what it is? Let’s use the [[Class]] property to see it:
(function() { alert( {}.toString.call(arguments) ) // [object Arguments] })()
This type is internally almost same as Object
. It only looks like array, because it’s keys are numeric and it has length
, but here the similarity ends.
Using arguments
as Array
There is still a way to call array methods on arguments
:
function sayHi() { var args = [].join.call(arguments, ':') alert(args) // 1:2:3 } sayHi(1,2,3)
Here we execute the join method of Array
in the context of arguments
, providing ':'
as first argument.
It works, because internally most array methods (and join
) use numeric indexes and length
to work.
The join
would work on a custom object as well, if the format is correct:
var obj = { 0: "A", 1: "B", length: 2 } alert( [].join.call(obj) ) // "A,B"
Making a real Array
The arr.slice(start, end) copies the part of arr
from start
to end
into the new array.
It can be called in the context of arguments
to copy all elements into a real array:
function sayHi() { var args = [].slice.call(arguments) // slice without parameters copies all alert( args.join(':') ) // now .join works } sayHi(1,2)
The arguments
and named parameters reference same values.
Updating arguments[..]
causes the corresponding parameter to change and vice versa. For example:
f(1) function f(x) { arguments[0] = 5 alert(x) // 5, updating arguments changed x }
And the reverse way:
f(1) function f(x) { x = 5 alert(arguments[0]) // 5, update of x reflected in arguments }
Array
methods which modify arguments
also modify local parameters:
sayHi(1) function sayHi(x) { alert(x) // 1 [].shift.call(arguments) alert(x) // undefined, no x any more :/ }
Actually, the modern ECMA-262 5th specification splits arguments from local variables. But as of now, browsers still behave like described above. Try the examples to see.
Generally, it is a good practice not to modify arguments
.
Default values
If you want a function parameter to be optional, there are two ways.
- First, you could check if for
undefined
and reassign:
function sayHi(who) { if (who === undefined) who = 'me' alert(who) }
- Or, use the OR
||
operator:function sayHi(who) { who = who || 'me' // if who is falsy, returns 'me' alert(who) } sayHi("John") sayHi() // 'me'
This way works great for parameters which are either not given or true. It happens most of time in real life.
A well-known function with variable number of arguments is Math.max
. It returns the greatest or it’s arguments:
alert( Math.max(1, -2, 7, 3) )
Use func.apply(context, args)
and Math.max
to find the greatest element of an array:
var arr = [1, -2, 7, 3] /* your code to output the greatest value of arr */
The solution:
var arr = [1, -2, 7, 3] alert( Math.max.apply(Math, arr) ) // (*)
Here, we call Math.max
, passing array of arguments args
.
In the “natural” call Math.max(...)
, the context this
is set to Math
, the object before dot '.'
. In our code we keep it same by explicitly passing to apply
.
Actually, talking about Math.max
- this method does not use the context at all. We could abuse that to simplify our code even further:
var arr = [1, -2, 7, 3] alert( Math.max.apply(0, arr) ) // dummy '0' context
Keyword arguments
Imagine you’ve got a function with several arguments. And most of them will have default values.
Like the following:
function showWarning(width, height, title, contents, showYesNo) { width = width || 200; // default values height = height || 100; var title = title || "Warning"; ... }
Here we have a function to show a warning window. It allows to specify width, height, title
, textual contents
and show the button if showYesNo
is set.
Most parameters have default values.
Here is how we use it:
showWarning(null, null, null, "Warning text", true) // or showWarning(200, 300, null, "Warning text")
The problem is: people tend to forget arguments order, and what they mean.
Imagine you have 10 arguments and most are optional. The function call becomes really terrifying.
The technique of keyword arguments exists in Python, Ruby and many other languages.
In JavaScript, it is implemented with a parameters object:
function showWarning(options) { var width = options.width || 200 // defaults var height = options.height || 100 var title = options.title || "Warning" // ... }
Calling such function is easy. You just pass an object of arguments like this:
showWarning({ contents: "Text", showYesNo: true })
Another bonus is that the arguments object can be reconfigured and reused:
var opts = { width: 400, height: 200, contents: "Text", showYesNo: true } showWarning(opts) opts.contents = "Another text" showWarning(opts) // another text with same options
Keyword arguments are employed in most frameworks.
Special arguments
properties
arguments.callee
There is an interesting property of arguments
, namely arguments.callee
. It references the function which is being run.
This property is deprecated by ECMA-262 in favor of named function expressions and for better performance.
JavaScript implementations can optimize the code much better if they know that keeping arguments.callee
is not required.
It will trigger error in “strict mode”, which you need to enable. Normally, it works.
Usually it is used for recursion in anonymous functions.
For example, setTimeout(func, ms)
is a built-in function which calls func
after ms
microseconds.
setTimeout( function() { alert(1) }, // alerts 1 after 1000 ms (=1 second) 1000 )
The function has no name. To call it recursively 3 times, let’s use arguments.callee
:
var i = 1 setTimeout( function() { alert(i) if (i++<3) setTimeout(arguments.callee, 1000) }, 1000 )
Another example is factorial:
// factorial(n) = n*factorial(n-1) var func = function(n) { return n==1 ? 1 : n*arguments.callee(n-1) }
The factorial function given above works even if
func
is reassigned to something else. That’s because it uses arguments.callee
to reference itself.
arguments.callee.caller
The property arguments.callee.caller keeps the reference to a calling function.
This property is deprecated by ECMA-262, for the same reason as arguments.caller
.
There is a property arguments.caller
with same meaning, but less supported. Don’t use it, stick to arguments.callee.caller
, all browsers have it.
In the example below, arguments.callee.caller
references the calling function of g
, that is f
.
f() function f() { alert(arguments.callee.caller) // undefined g() } function g() { alert(arguments.callee.caller) // f }