JavaScript/Functions
A function is a block of code that solves a dedicated problem and returns the solution to the calling statement. The function exists in its own context. Hence, functions divide massive programs into smaller 'bricks' which structure the software as well as the software development process.
// define a function
function <function_name> (<parameters>) {
<function_body>
}
// call a function
<variable> = <function_name> (<arguments>);
JavaScript supports the software development paradigm functional programming. Functions are a data type derived from Object; they can be bound to variables, passed as arguments, and returned from other functions, just as any other data type.
Declaration
[edit | edit source]Functions can be constructed in three main ways. The first version can be abbreviated further; see below.
The conventional way:
"use strict";
// conventional declaration (or 'definition')
function duplication(p) {
return p + "! " + p + "!";
}
// call the function
const ret = duplication("Go");
alert(ret);
Construction via a variable and an expression:
"use strict";
// assign the function to a variable
let duplication = function (p) {
return p + "! " + p + "!";
};
const ret = duplication("Go");
alert(ret);
Construction via the new
operator (this version is a little cumbersome):
"use strict";
// using the 'new' constructor
let duplication = new Function ("p",
"return p + '! ' + p + '!'");
const ret = duplication("Go");
alert(ret);
Invocation
[edit | edit source]For the declaration of functions, we have seen 3 variants. For their invocation, there are also 3 variants. The declarations and invocations are independent of each other and you can arbitrarily combine them.
The conventional invocation variant uses the function name followed by parenthesis ( )
. Within the parenthesis, there are the function's arguments, if any exist.
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// the conventional invocation method
const ret = duplication("Go");
alert(ret);
If the script runs in a browser, there are two more possibilities. They use the window
object that is provided by the browser.
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
// via 'call'
let ret = duplication.call(window, "Go");
alert(ret);
// via 'apply'
ret = duplication.apply(window, ["Go"]);
alert(ret);
Hint: If you use the function name without the parenthesis ()
, you will receive the function itself (the script), not any result of an invocation.
"use strict";
function duplication(p) {
return p + "! " + p + "!";
}
alert(duplication); // 'function duplication (p) { ... }'
Hoisting
[edit | edit source]Functions are subject to 'hoisting'. This mechanism transfers the declaration of a function automatically to the top of its scope. As a consequence, you can call a function from an upper place in the source code than its declaration.
"use strict";
// use a function above (in source code) its declaration
const ret = duplication("Go");
alert(ret);
function duplication(p) {
return p + "! " + p + "!";
}
Immediately Invoked Function
[edit | edit source]So far, we have seen the two separate steps declaration and invocation. There is also a syntax variant that allows the combination of both. It is characterized by using parenthesis around the function declaration followed by ()
to invoke that declaration.
"use strict";
alert( // 'alert' to show the result
// declaration plus invocation
(function (p) {
return p + "! " + p + "!";
})("Go") // ("Go"): invocation with the argument "Go"
);
alert(
// the same with 'arrow' syntax
((p) => {
return p + "! " + p + "!";
})("Gooo")
);
This syntax is known as a Immediately Invoked Function Expression (IIEF).
Arguments
[edit | edit source]When functions are called, the parameters from the declaration phase are replaced by the arguments of the call. In the above declarations, we used the variable name p
as a parameter name. When calling the function, we mostly used the literal "Go" as the argument. At runtime, it replaces all occurrences of p
in the function. The above examples demonstarte this technique.
Call-by-value
[edit | edit source]Such substitutions are done 'by value' and not 'by reference'. A copy of the argument's original value is passed to the function. If this copied value is changed within the function, the original value outside of the function is not changed.
"use strict";
// there is one parameter 'p'
function duplication(p) {
// In this example, we change the parameter's value
p = "NoGo";
alert("In function: " + p);
return p + "! " + p + "!";
};
let x = "Go";
const ret = duplication(x);
// is the modification of the argument done in the function visible here? No.
alert("Return value: " + ret + " Variable: " + x);
For objects (all non-primitive data types), this 'call-by-value' has a - possibly - astonishing effect. If the function modifies a property of the object, this change is also seen outside.
"use strict";
function duplication(p) {
p.a = 2; // change the property's value
p.b = 'xyz'; // add a property
alert("In function: " + JSON.stringify(p));
return JSON.stringify(p) + "! " + JSON.stringify(p) + "!";
};
let x = {a: 1};
alert("Object: " + JSON.stringify(x));
const ret = duplication(x);
// is the modification of the argument done in the function visible here? Yes.
alert("Return value: " + ret + " Object: " + JSON.stringify(x));
When the example runs, it shows that after the invocation of duplication
, the changes made by the function are visible not only in the return value. Also, the properties of the original object x
have changed. Why this; is it different from the behavior concerning primitive data types? No.
The function receives a copy of the reference to the object. Hence, within the function, the same object is referenced. The object itself exists only one time, but there are two (identical) references to the object. It does not make a difference whether the object's properties are modified by one reference or the other.
Another consequence is - and this may be intuitively the same as with primitive data types (?) - that the modification of the reference itself, e.g., by the creation of a new object, will not be visible in the outer routine. The reference to the new object is stored in the copy of the original reference. Now we have not only two references (with different values) but also two objects.
"use strict";
function duplication(p) {
// modify the reference by creating a new object
p = {};
p.a = 2; // change the property's value
p.b = 'xyz'; // add a property
alert("In function: " + JSON.stringify(p));
return JSON.stringify(p) + "! " + JSON.stringify(p) + "!";
};
let x = {a: 1};
alert("Object: " + JSON.stringify(x));
const ret = duplication(x);
// is the modification of the argument done in the function visible here? No.
alert("Return value: " + ret + " Object: " + JSON.stringify(x));
Note 1: The naming of this argument-passing technique is not used consistently across different languages. Sometimes it is called 'call-by-sharing'. Wikipedia gives an overview.
Note 2: The described consequences of JavaScript's argument-passing are comparable with the consequences of using the keyword const
, which declares a variable to be a constant. Such variables cannot be changed. Nevertheless, if they refer to an object, the object's properties can be changed.
Default values
[edit | edit source]If a function is invoked with fewer arguments than it contains parameters, the surplus parameters keep undefined. But you can define default values for this case by assigning a value within the function's signature. The missing parameters will get those as their default values.
"use strict";
// two nearly identical functions; only the signature is slightly different
function f1(a, b) {
alert("The second parameter is: " + b)
};
function f2(a, b = 10) {
alert("The second parameter is: " + b)
};
// identical invocations; different results
f1(5); // undefined
f1(5, 100); // 100
f2(5); // 10
f2(5, 100); // 100
Variable number of arguments
[edit | edit source]For some functions, it is 'normal' that they get involved with different numbers of arguments. For example, think of a function that shows names. firstName
and familyName
must be given in any case, but it's also possible that an academicTitle
or a titleOfNobility
must be shown. JavaScript offers different possibilities to handle such situations.
Individual checks
[edit | edit source]The 'normal' parameters, as well as the additional parameters, can be checked to determine whether they contain a value or not.
"use strict";
function showName(firstName, familyName, academicTitle, titleOfNobility) {
"use strict";
// handle required parameters
let ret = "";
if (!firstName || !familyName) {
return "first name and family name must be specified";
}
ret = firstName + ", " + familyName;
// handle optional parameters
if (academicTitle) {
ret = ret + ", " + academicTitle;
}
if (titleOfNobility) {
ret = ret + ", " + titleOfNobility;
}
return ret;
}
alert(showName("Mike", "Spencer", "Ph.D."));
alert(showName("Tom"));
Every single parameter that may not be given must be individually checked.
The 'rest' parameter
[edit | edit source]If the handling of the optional parameters is structurally identical, the code can be simplified by using the rest operator syntax - mostly in combination with a loop. The syntax of the feature consists of three dots in the function's signature - like in the spread syntax.
How does it work? As part of the function invocation, the JavaScript engine combines the given optional arguments into a single array. (Please note that the calling script does not use an array.) This array is given to the function as the last parameter.
"use strict";
// the three dots (...) introduces the 'rest syntax'
function showName(firstName, familyName, ...titles) {
// handle required parameters
let ret = "";
if (!firstName || !familyName) {
return "first name and family name must be specified";
}
ret = firstName + ", " + familyName;
// handle optional parameters
for (const title of titles) {
ret = ret + ", " + title;
}
return ret;
}
alert(showName("Mike", "Spencer", "Ph.D.", "Duke"));
alert(showName("Tom"));
The third and all following arguments of the call are collected into a single array that is available in the function as the last parameter. This allows the use of a loop and simplifies the function's source code.
The 'arguments' keyword
[edit | edit source]In accordance with other members of the C-family of programming languages, JavaScript offers the keyword arguments
within functions. It is an Array-like object that contains all given arguments of a function call. You can loop over it or use its length
property.
Its functionality is comparable with the above rest syntax. The main difference is that arguments
contains all arguments, whereas the rest syntax affects not necessarily all arguments.
"use strict";
function showName(firstName, familyName, academicTitles, titlesOfNobility) {
// handle ALL parameters with a single keyword
for (const arg of arguments) {
alert(arg);
}
}
showName("Mike", "Spencer", "Ph.D.", "Duke");
Return
[edit | edit source]The purpose of a function is to provide a solution of a dedicated problem. This solution is given back to the calling program by the return
statement.
Its syntax is return <expression>
, where <expression>
is optional.
A function runs until it reaches such a return
statement (, or an uncatched exception occurs, or behind the last statement). The <expression>
may be a simple variable of any data type like return 5
, or a complex expression like return myString.length
, or is omitted entirely: return
.
If there is no <expression>
within the return
statement, or if no return
statement is reached at all, undefined
is returned.
"use strict";
function duplication(p) {
if (typeof p === 'object') {
return; // return value: 'undefined'
}
else if (typeof p === 'string') {
return p + "! " + p + "!";
}
// implicit return with 'undefined'
}
let arg = ["Go", 4, {a: 1}];
for (let i = 0; i < arg.length; i++) {
const ret = duplication(arg[i]);
alert(ret);
}
Arrow functions (=>)
[edit | edit source]Arrow functions are a compact alternative to the above-shown conventional function syntax. They abbreviate some language elements, omit others, and have only a few semantic distinctions in comparison to the original syntax.
They are always anonymous but can be assigned to a variable.
"use strict";
// original conventional syntax
function duplication(p) {
return p + "! " + p + "!";
}
// 1. remove keyword 'function' and function name
// 2. introduce '=>' instead
// 3. remove 'return'; the last value is automatically returned
(p) => {
p + "! " + p + "!"
}
// remove { }, if only one statement
(p) => p + "! " + p + "!"
// Remove parameter parentheses if it is only one parameter
// -----------------------------
p => p + "! " + p + "!" // that's all!
// -----------------------------
alert(
(p => p + "! " + p + "!")("Go")
);
Here is one more example using an array. The forEach
method loops over the array and produces one array-element after the next. This is put to the arrow function's single argument e
. The arrow function shows e
together with a short text.
"use strict";
const myArray = ['a', 'b', 'c'];
myArray.forEach(e => alert("The element of the array is: " + e));
Other programming languages offer the concept of arrow functions under terms like anonymous functions or lambda expressions.
Recursive Invokations
[edit | edit source]Functions can call other functions. In real applications, this is often the case.
A particular situation occurs when they call themselves. This is called a recursive invokation. Of course, this implies the danger of infinite loops. You must change something in the arguments to avoid this hurdle.
Typically the need for such recursive calls arises when an application handles tree structures like bill of materials, a DOM tree, or genealogy information. Here we present the simple to implement mathematical problem of factorial computation.
The factorial is the product of all positive integers less than or equal to a certain number , written as . For example, . It can be solved by a loop from to , but there is also a recursive solution. The factorial of is the already computed factorial of multiplied with , or in formulas: . This thought leads to the correspondent recursive construction of the function:
"use strict";
function factorial(n) {
if (n > 0) {
const ret = n * factorial(n-1);
return ret;
} else {
// n = 0; 0! is 1
return 1;
}
}
const n = 4;
alert(factorial(n));
As long as is greater than , the script calls factorial
again, but time with as the argument. Therefore the arguments converge to . When is reached, this is the first time the factorial
function is not called again. It returns the value of . This number is multiplied by the next higher number from the previous invocation of factorial
. The result of the multiplication is returned to the previous invocation of factorial
, ... .