Table of Contents
Function parameters and arguments are among the most confusing terms. This post will help you learn about what parameters and arguments are and the difference between them. You will also learn about arguments object, rest and default parameters and value and reference types.
Function parameters
Parameters are part of a function definition. When you declare a function, you can also specify a list of variables the function accepts. These variables are called function parameters, or parameters. Function can have unlimited number of parameters. When you have more than one parameter, you separate them with commas.
// Function definition syntax (declaration):
function functionName(param1, param2) {
// function body
}
// Function definition syntax (expression):
const functionName = function(param1, param2) {
// function body
}
// Function without parameters:
function generateRandomNumber() {
return Math.floor(Math.random() * 1000)
}
// Function with one parameter:
function generateRandomNumberWithLimit(upperLimit) {
return Math.floor(Math.random() * upperLimit)
}
// Function with two parameters:
function multiplyNumbers(a, b) {
return a * b
}
Function arguments
When you invoke some function, and you pass some values to that function, these values are called function arguments, or arguments.
// Create a function:
function divideNumbers(x, y) {
return x / y
}
// Invoke divideNumbers function:
// The 65623 and 432 are arguments
// passed to the divideNumbers
divideNumbers(65623, 432)
// Output:
// 151.90509259259258
When you pass some argument to a function, few things happen on the background. First, JavaScript will create new variables based on the function’s parameters, using names of the parameters for these variables. Second, JavaScript will initialize these variables using the argument you passed when you called the function.
During this initialization process, JavaScript will copy any primitive values by value. If you pass any objects, JavaScript will copy them by reference. These variables will be local to the function. They will exist only inside it. This means that you will not be able to access any of these variables from the outside.
These variables will exist only during the function call. Once the function call is finished, these variables will be lost.
Values and references
Above, I mentioned that primitive values and object are handled differently when you pass them as arguments. In JavaScript, there are two types of data. There are primitive data types, such as string, number, bigint, boolean, symbol, undefined, and null. Then, there are objects.
Primitive data types also belong to a group of “value types”. Objects belong to a group called “reference types”. Value types are copied by value. Reference type are copied by reference. This matters in the terms of function parameters and arguments because it can lead to unintended consequences.
Function arguments and value types
When you pass a primitive value to a function, JavaScript will create its copy and assign it to a variable local to the function. This means that there will now be two identical pieces of data. One will be the original and the second will be the copy. Let’s say that you try to change the copy, inside the function.
In case of value types, the original will stay intact. The change you made inside the function, to the argument, will not have any effect on the original.
// Create a variable and assign it a primitive value:
const num = 7
// Create a function with one parameter:
function randomFunc(randomNumber) {
// Try to change the value of "randomNumber":
randomNumber = 11
// Return the new value of "randomNumber":
return randomNumber
}
// Invoke the "randomFunc":
randomFunc(limit)
// Output:
// 11
// Log the value of "num" variable:
console.log(num)
// Output:
// 7
Function arguments and reference types
This doesn’t apply to reference types, or objects. Let’s say you declare a variable and assign it an object. You then pass that variable as an argument to a function. Object is a reference type. This means that you are not passing the whole object. Instead, you are passing only a reference to that object.
When you pass the object’s reference, this is also what JavaScript will copy and assign to the variable local to the function. At this moment, there are not two pieces of data. There is still only one piece of data, the original. However, there are two references to that original. This is where problems begin.
If you now, accidentally or intentionally, try to change the argument, the object, the change will not remain local. The change will actually alter the original object.
// Create a simple object:
const user = {
name: 'joe',
email: 'joe@joe.ci',
loggedIn: false
}
// Create a function with one parameter:
function randomFunc(userObj) {
// Try to change the value of "loggedIn" property
// that exists on the userObj argument:
userObj.loggedIn = true
// Return the altered userObj argument:
return userObj
}
// Invoke the "randomFunc":
randomFunc(user)
// Output:
// {
// name: 'joe',
// email: 'joe@joe.ci',
// loggedIn: true // <= The value of "loggedIn" property changed
// }
// Log the value of the original "user" object:
console.log(user)
// Output:
// {
// name: 'joe',
// email: 'joe@joe.ci',
// loggedIn: true // <= The value of "loggedIn" property changed
// }
In the example above, we tried to change the value of loggedIn
property on the argument object. The result was that we also changed the value of the loggedIn
on the original object. This is what was supposed to happen because we were working with two references to the same object, not two objects.
We can prevent this, while still be able to work with the object. We can create a real copy of the object manually and then change the copy. One way to do this is by using the spread syntax.
// Create a simple object:
const user = {
name: 'joe',
email: 'joe@joe.ci',
loggedIn: false
}
// Create a function with one parameter:
function randomFunc(userObj) {
// Create real copy of userObj using spread syntax:
const newUserObj = { ...userObj }
// Change the value of "loggedIn" property
// that exists on the "newUserObj" object:
newUserObj.loggedIn = true
// Return the altered "newUserObj" object:
return newUserObj
}
// Invoke the "randomFunc":
randomFunc(user)
// Output:
// {
// name: 'joe',
// email: 'joe@joe.ci',
// loggedIn: true // <= The value of "loggedIn" property changed
// }
// Log the value of the original "user" object:
console.log(user)
// Output:
// {
// name: 'joe',
// email: 'joe@joe.ci',
// loggedIn: false // <= The value of "loggedIn" property stayed the same
// }
Function arguments !== function parameters
By now, it probably makes sense that function parameters and arguments are different things. However, this is not what I mean. What I actually mean is this: JavaScript doesn’t check the number of parameters you define and the number of arguments you pass. For example, let’s say you create a function with two parameters.
You can call this function and pass in 10 arguments. JavaScript will not care. It will happily invoke the function, create and assign variables for all parameters and execute the function. Not a single error thrown. What if you pass fewer arguments than there are parameters? Those arguments you omit will be set to undefined
.
// Create a function with three parameters:
function myFunc(param1, param2, param3) {
// Return all parameters as an array:
return [param1, param2, param3]
}
// Invoke "myFunc" with all three parameters:
myFunc('one', 'two', 'three')
// Output:
// [ 'one', 'two', 'three' ]
// Invoke "myFunc" with two parameters:
myFunc('one', 'two')
// Output:
// [ 'one', 'two', undefined ]
// Invoke "myFunc" with five parameters:
myFunc('one', 'two', 'three', 'four', 'five')
// Output:
// [ 'one', 'two', 'three' ]
The point of this is simple. When you invoke a function, make sure to pass all required arguments. Remember that JavaScript will not warn you when you miss some arguments or pass more arguments than is necessary.
The arguments object
When you call a function, few interesting things happen. One of them is that JavaScript will also create an array-like object called arguments
. This object contains all arguments you passed into the function. You can use this object to access every argument and its value.
// Create a function:
function assembleTeam(member1, member2, member3) {
// Log the whole arguments object:
console.log(arguments)
// Log the length of arguments object:
console.log('length: ', arguments.length)
// Return new team as an array:
return `Team: [${member3}, ${member2}, ${member1}]`
}
// Invoke the "assembleTeam" function:
assembleTeam('Clark', 'Cindy', 'Joshua')
// Output:
// {
// '0': 'Clark',
// '1': 'Cindy',
// '2': 'Joshua',
// length: 3,
// callee: ƒ assembleTeam(),
// __proto__: {
// constructor: ƒ Object(),
// __defineGetter__: ƒ __defineGetter__(),
// __defineSetter__: ƒ __defineSetter__(),
// hasOwnProperty: ƒ hasOwnProperty(),
// __lookupGetter__: ƒ __lookupGetter__(),
// __lookupSetter__: ƒ __lookupSetter__(),
// isPrototypeOf: ƒ isPrototypeOf(),
// propertyIsEnumerable: ƒ propertyIsEnumerable(),
// toString: ƒ toString(),
// valueOf: ƒ valueOf(),
// toLocaleString: ƒ toLocaleString()
// }
// }
// 'length: ' 3
// 'Team: [Joshua, Cindy, Clark]'
Note that we are talking about all arguments you passed, not all required arguments. If you pass more arguments than you should, the arguments
object will contains also these additional arguments. This means that the arguments
object provides you with a way to access any additional arguments, via array-like indices.
// Create a function:
function readNumbers(number1, number2, number3) {
// Log the whole arguments object:
console.log(arguments)
// Log the 7th argument:
console.log(arguments[7])
// Return all numbers as an array:
return `Numbers are: [${number1}, ${number2}, ${number3}]`
}
// Invoke the "readNumbers" function:
readNumbers(1, 2, 3, 4, 5, 6, 7, 8, 9)
// Output:
// {
// '0': 1,
// '1': 2,
// '2': 3,
// '3': 4,
// '4': 5,
// '5': 6,
// '6': 7,
// '7': 8,
// '8': 9,
// length: 9,
// callee: ƒ readNumbers(),
// __proto__: {
// constructor: ƒ Object(),
// __defineGetter__: ƒ __defineGetter__(),
// __defineSetter__: ƒ __defineSetter__(),
// hasOwnProperty: ƒ hasOwnProperty(),
// __lookupGetter__: ƒ __lookupGetter__(),
// __lookupSetter__: ƒ __lookupSetter__(),
// isPrototypeOf: ƒ isPrototypeOf(),
// propertyIsEnumerable: ƒ propertyIsEnumerable(),
// toString: ƒ toString(),
// valueOf: ƒ valueOf(),
// toLocaleString: ƒ toLocaleString()
// }
// }
// 'The 7th argument is: 8'
// 'Numbers are: [1, 2, 3]'
If you pass fewer arguments than you should, arguments you omit JavaScript will set them to undefined
. That said, these missing arguments will not appear in the arguments
object.
// Create a function:
function readNumbers(number1, number2, number3) {
// Log the whole arguments object:
console.log(arguments)
// Return all numbers as an array:
return `Numbers are: [${number1}, ${number2}, ${number3}]`
}
// Invoke the "readNumbers" function with a single argument:
readNumbers(1)
// Output:
// {
// '0': 1,
// length: 1,
// callee: ƒ readNumbers(),
// __proto__: {
// constructor: ƒ Object(),
// __defineGetter__: ƒ __defineGetter__(),
// __defineSetter__: ƒ __defineSetter__(),
// hasOwnProperty: ƒ hasOwnProperty(),
// __lookupGetter__: ƒ __lookupGetter__(),
// __lookupSetter__: ƒ __lookupSetter__(),
// isPrototypeOf: ƒ isPrototypeOf(),
// propertyIsEnumerable: ƒ propertyIsEnumerable(),
// toString: ƒ toString(),
// valueOf: ƒ valueOf(),
// toLocaleString: ƒ toLocaleString()
// }
// }
// 'Numbers are: [1, undefined, undefined]'
The rest parameter
Starting with ES6, you can also use the rest parameter to specify that a function accepts indefinite number of parameters. This rest parameter will create an array that will contain all arguments. You can use the name of the rest parameter to access those arguments.
One thing to mention. If you use rest parameter along with regular parameters, rest parameter must always come as last. Different order will lead to an error.
// Create a function with rest parameter:
function readNumbers(...numbers) {
return numbers
}
// Invoke "readNumbers" function:
readNumbers(5, 8, 99, 63)
// Output:
// [ 5, 8, 99, 63 ]
// Create a function with normal and also rest parameter:
function readNumbers(number1, number2, ...numbers) {
return [number1, number2, numbers]
}
// Invoke "readNumbers" function:
readNumbers(15, 18, 89, 639)
// Output:
// [ 15, 18, [ 89, 639 ] ]
As you can see, the rest parameter is not the same as the arguments
object. One difference is that rest parameter is a real array. You can iterate over it with methods like map()
, sort()
, reduce()
, forEach()
. Try that with arguments
object. Another difference is the content.
The arguments
object contains all arguments passed into a function. The rest parameter contains only the extra arguments that don’t have matching parameters. So, let’s say you have a function that has one parameter and then rest parameter. If you pass in three arguments, rest parameter will contain only the last two.
The arguments
object, on the other hand, will contain all arguments, the first normal as well as the rest.
// Create a function with normal and also rest parameter:
function readNumbers(num1, num2, ...nums) {
console.log('num1: ', num1)
console.log('num2: ', num2)
console.log('nums: ', nums)
}
// Invoke "readNumbers" function:
readNumbers(1, 3, 5, 7, 12)
// Output:
// 'num1: ' 1
// 'num2: ' 3
// 'nums: ' [ 5, 7, 12 ]
// Create a function with two parameters:
function readNumbers(num1, num2) {
console.log(arguments)
}
// Invoke "readNumbers" function:
readNumbers(1, 3, 5, 7, 12)
// Output:
// {
// '0': 1,
// '1': 3,
// '2': 5,
// '3': 7,
// '4': 12,
// length: 5,
// callee: ƒ readNumbers(),
// __proto__: {
// constructor: ƒ Object(),
// __defineGetter__: ƒ __defineGetter__(),
// __defineSetter__: ƒ __defineSetter__(),
// hasOwnProperty: ƒ hasOwnProperty(),
// __lookupGetter__: ƒ __lookupGetter__(),
// __lookupSetter__: ƒ __lookupSetter__(),
// isPrototypeOf: ƒ isPrototypeOf(),
// propertyIsEnumerable: ƒ propertyIsEnumerable(),
// toString: ƒ toString(),
// valueOf: ƒ valueOf(),
// toLocaleString: ƒ toLocaleString()
// }
// }
Default parameters
JavaScript will set any omitted arguments to undefined
. You can avoid this by creating a fallback value. You can achieve this by using default parameters. When you define a function, and its parameters, you can define a default value for each parameter by using assignment operator and some value.
Later, when you call the function and don’t provide required argument, JavaScript will use the default value instead of undefined
. If you do provide some value, JavaScript will use that value, instead of the default.
// Create a function with one parameter
// and set the default value of the parameter to "Anonymous":
function greetUser(username = 'Anonymous') {
return `Hello, ${username}!`
}
// Invoke the "greetUser" function without argument:
greetUser()
// Output:
// 'Hello, Anonymous!'
// Invoke the "greetUser" function with an argument:
greetUser('Timothy')
// Output:
// 'Hello, Timothy!'
Conclusion: Function parameters and arguments in JavaScript
Function parameters and arguments can be confusing. I hope that this post made it easier for you to understand what parameters and arguments are and how they differ. I also hope that this post helped you learn how to use arguments object and rest and default parameters.
If you liked this article, please subscribe so you don't miss any future post.
If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂