Parameters and arguments
Distinction between parameters and arguments
Parameters are part of the function's definition. A list of zero or more parameters, separated by commas, in which each parameter is a named reference to a particular data value. The parameters represent the input values of the function. The scope of a parameter is the function; parameters cannot be used outside the function.
Arguments are part of a function call. A list of zero or more arguments, separated by commas, in which each argument is passed to the function as a value to be assigned to one of the parameters of the function.
The arguments are assigned to the parameters in equal order, left-to-right: argument one is assigned to parameter one, argument two is assigned to parameter two etc.
If there are more arguments than parameters, then the idle arguments are ignored. If there are less arguments than parameters,
then the parameters that are not covered get the value undefined
assigned.
This way parameters get assigned an initial value (they are initialized). A parameter can be reassigned any value any number of times within the function.
Pass by value
It is often stated that in JavaScript arguments, in case of primitive values, are passed by value and that in case of objects arguments are passed by reference. Other sources may argue that in JavaScript all arguments are passed by value. In some programming languages you can choose to pass an argument by value or by reference. If an argument is passed by reference, then the corresponding parameter is bound to a reference to the variable (as opposed to a value) passed by the argument. Assigning a new value to the parameter (i.e. the variable) inside the function changes the variable's value inside and outside the function. This is never the case in JavaScript. In JavaScript arguments are never passed by reference.
let value = 2;
const name = {
firstName: "John",
lastName: "Doe"
}
function square(number) {
return number *= number;
}
function changeName(object) {
object = { firstName: "Jane", lastName: "Doe" }
return object.firstName;
}
console.log(square(value)); // logs: 4
console.log(value); // logs: 2 // The variable outside the function has not been changed
console.log(changeName(name)); // logs: "Jane"
console.log(name.firstName); // logs: "John" // The variable outside the function has not been changed
If an argument is passed by value, then typically the argument's value is copied into a new memory address location which is assigned to the corresponding parameter. Assigning a new value to the parameter inside the function does not affect the passed variable's value outside the function. Considering the above example, this seems to be the case in JavaScript. However, the next example shows that changing an object's properties inside the function does affect the object's properties outside the function.
const name = {
firstName: "John",
lastName: "Doe"
}
function changeFirstName(object) {
return object.firstName = "Jane";
}
console.log(changeFirstName(name)); // logs: "Jane"
console.log(name.firstName); // logs: "Jane" // The variable (constant) outside the function has been changed!
PS: Note that now the object's property has been changed, instead of a whole new value being assigned to the parameter.
JavaScript uses what is sometimes referred to as pass by sharing or "copy of reference". Essentially, in JavaScript arguments are passed to functions by value, but in "Value data types and reference data types" we have seen that primitive values are stored in a different way than object values. Object arguments are passing a reference to an object value, as opposed to passing the object value itself. The same object value is shared by both the variable outside the function and the receiving parameter. Note: not a reference to a variable is shared, but a reference to its object value!. So, both the variable and the parameter "point" to the same object value. If the code within the body of the function assigns a new value to a parameter, that parameter gets a new primitive value or a new reference to a new object value. Variable and parameter do not share a reference to the same object value anymore. But when there is no new value assigned to the parameter, changing the object's property within the function also changes that property of the object outside the function, because it is the same shared object value.
In JavaScript an array is also a (mutable) object. So also with arrays; if the function changes any of the elements of a parameter's array value or changes its number of elements, that change is visible outside the function (provided the parameter was not re-assigned a value within the function):
const myArray = ["John"];
function myFunc(array) {
array[0] = "Jane";
array[1] = "Doe";
};
console.log(myArray[0]); // logs: "John"
console.log(myArray.length); // logs: 1
myFunc(myArray);
console.log(myArray[0]); // logs: "Jane"
console.log(myArray.length); // logs: 2
We will experience the same behavior when we try to simply "copy" objects. The object value will not be copied but a reference to the same object value will be copied. Both variables will share a reference to the same object value. You can modify the object by either referring to the original variable or to the "copied" one.
const myArray = ["John"];
const copyMyArray = myArray;
console.log(myArray[0]); // logs: "John"
copyMyArray[0] = "Jane"; // changing copyMyArray also changes the original myArray:
console.log(myArray[0]); // logs: "Jane"
console.log(myArray === copyMyArray); // logs: true
If you want to pass an object to a function and have the function operate on the object's properties, without changing the original object,
you will need to make a real copy of the object value first.
You can copy an object
by using a for...in
loop or Object.assign()
(among other ways).
You can copy an array
by using a for...of
loop, forEach()
or Array.from()
(among other ways).
const name = {
firstName: "John",
lastName: "Doe"
}
function changeFirstName(object) {
let objectCopy = Object.assign({},object);
objectCopy.firstName = "Jane";
return objectCopy.firstName;
}
console.log(changeFirstName(name)); // logs: "Jane"
console.log(name.firstName); // logs: "John" // The constant outside the function has not been changed!
Be aware that any of these operations create a "shallow copy", meaning that if an object property again is a reference to an object, both the original and the copy share the same property value. Copying objects and arrays, including shallow and deep copies, will be explained in more detail later this tutorial.
Default parameters
Parameters can be set to initialize with default values if no value or undefined
is passed.
This overwrites the standard default initialization of undefined
.
function subtract(a = 0, b = 0) {
return a - b;
}
console.log(subtract(2)); // logs: 2
console.log(subtract()); // logs: 0
The next code does something similar:
function subtract(a, b) {
a = (typeof a !== 'undefined') ? a : 0;
b = (typeof b !== 'undefined') ? b : 0;
return a - b;
}
console.log(subtract(2)); // logs: 2
console.log(subtract()); // logs: 0
Note that the function only uses the default initialization when no value or undefined
is passed.
function add(a = 2, b = 3) {
return a + b;
}
console.log(add(undefined)); // logs: 5
console.log(add(null, "")); // logs: "null"
console.log(add("Hello")); // logs: "Hello3"
console.log(add(false, true)); // logs: 1
Also note that parameters are still assigned, left-to-right, in equal order as the arguments, regardless whether parameters have defaults set or not.
function myFuntion(a = 1, b) {
return [a, b];
}
console.log(myFuntion(5)); // logs: [5, undefined]
Default values can use previous parameters (to the left) in the parameters list:
function logic(a, b = a && true) {
if(a) { return b }
else { return a || b; }
}
console.log(logic(true)); // logs: true
console.log(logic(false, true)); // logs: true
The parameters are in their own scope: a parent scope of the scope of the function body. Defaults for the parameters cannot access functions or variables declared in the function body, but reversed, default values (including functions) are available inside the function body.
function myFunction(a = b) {
var b = 2;
}
myFunction(); // ReferenceError: b is not defined
Rest parameters
The rest parameter syntax allows a function to accept a variable number of arguments. It is JavaScript's way to support variadic functions.
function myFunction(...someIdentifier) {
someIdentifier.forEach(function(element) {
console.log(element)
})
}
myFunction(1, true, "Hello", Math.PI);
// logs:
// 1
// true
// "Hello"
// 3.141592653589793
As you can see in the above example, a parameter prefixed with three full stops, the rest parameter, gets an array assigned in which all (remaining) arguments are placed. This array can be used inside the function body.
There can only be one rest parameter in the parameter list, and the rest parameter needs to be the last parameter in the parameter list. The rest parameter always creates an array, even if there are no arguments provided for the rest parameter.
function myFunction(a, ...remainingArgs) {
console.log(remainingArgs)
}
myFunction("oneAndOnlyArg"); // logs: [] // empty array
Two more examples using a rest parameter:
function personalGreeting(greeting, ...names) {
return names.map(function(element) {
return greeting+" "+element
})
}
console.log(personalGreeting("Hello", "John", "Jane", "Abby", "George")); // logs: [ "Hello John", "Hello Jane", "Hello Abby", "Hello George" ]
console.log(personalGreeting("Hi", "Casey", "Noah")); // logs: [ "Hi Casey", "Hi Noah" ]
BTW: The above example uses the Array.map()
method.
function productOfSequence(...factors) {
let product = 1;
for (const factor of factors) {
product *= factor;
}
return product;
}
console.log(productOfSequence(2, 4, 6)); // logs: 48
console.log(productOfSequence(1, 2, 3, 4)); // logs: 24
Spread syntax
Spread syntax can be used when all elements from an iterable object, like an array, should be applied one-by-one in a function call's arguments list. Spread syntax looks exactly like rest syntax and, in a way, is the opposite of rest syntax. Rest syntax is used for parameters, spread syntax is used for arguments.
In the next example an array and string (both are iterable) are spread in function parameters.
For arrays (not for other iterable objects) this is equivalent to using
Function.prototype.apply(null, args)
, as we will explain later this tutorial.
function myFunction(first, second, third) {
return `first = ${first}, second = ${second} and third = ${third}.`
}
const args = [true, false];
const nums = [0, 1, 2];
const str = "🤣LOL";
console.log(myFunction(...args)); // logs: "first = true, second = false and third = undefined."
console.log(myFunction(...nums)); // logs: "first = 0, second = 1 and third = 2."
console.log(myFunction(...str)); // logs: "first = 🤣, second = L and third = O."
console.log(myFunction.apply(null, nums)); // logs: "first = 0, second = 1 and third = 2."
console.log(myFunction.apply(null, str)); // logs: TypeError: second argument to Function.prototype.apply must be an array
PS: Note that spreading a string in function parameters treats surrogate pairs correctly.
Beware the risk of exceeding the JavaScript engine's argument length limit. The consequences of calling a function with more arguments than the maximum limit (that is, more than tens of thousands of arguments) varies across engines. Some engines will throw an error, others will arbitrarily cut the argument length, possibly leading to unexpected behavior.
Spread syntax can also be used when all elements from an iterable object, like an array, need to be included in a new array or object. Later more about this.
Callback functions
A function can be passed as an argument into another function. A parameter inside the function then refers to the outside, passed in function. This outside function is called a callback function if it is "called back" (executed) from within the other function.
function doubleIt(nr) {
alert(`Two times ${nr} is ${nr * 2}`);
}
function squareIt(nr) {
alert(`${nr} squared is ${nr ** 2}`);
}
function processUserInput(callback) {
const number = prompt("Please enter a number");
callback(number);
}
processUserInput(doubleIt);
processUserInput(squareIt);