034.3 Lesson 2
Certificate: |
Web Development Essentials |
---|---|
Version: |
1.0 |
Topic: |
034 JavaScript Programming |
Objective: |
034.3 JavaScript Control Structures and Functions |
Lesson: |
2 of 2 |
Introduction
In addition to the standard set of builtin functions provided by the JavaScript language, developers can write their own custom functions to map an input to an output suitable to the application’s needs. Custom functions are basically a set of statements encapsulated to be used elsewhere as part of an expression.
Using functions is a good way to avoid writing duplicate code, because they can be called from different locations throughout the program. Moreover, grouping statements in functions facilitates the binding of custom actions to events, which is a central aspect of JavaScript programming.
Defining a Function
As a program grows, it becomes harder to organize what it does without using functions. Each function has its own private variable scope, so the variables defined inside a function will be available only inside that same function. Thus, they won’t get mixed up with variables from other functions. Global variables are still accessible from within functions, but the preferable way to send input values to a function is through function parameters. As an example, we are going to build on the prime number validator from the previous lesson:
// A naive prime number tester
// The number we want to evaluate
let candidate = 231;
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Display the result in the console window
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
If later in the code you need to check if a number is prime, it would be necessary to repeat the code that has already been written. This practice is not recommended, because any corrections or improvements to the original code would need to be replicated manually everywhere the code was copied to. Moreover, repeating code places a burden on the browser and network, possibly slowing down the display of the web page. Instead of doing this, move the appropriate statements to a function:
// A naive prime number tester function
function test_prime(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
The function declaration starts with a function
statement, followed by the name of the function and its parameters. The name of the function must follow the same rules as the names for variables. The parameters of the function, also known as the function arguments, are separated by commas and enclosed by parenthesis.
Tip
|
Listing the arguments in the function declaration is not mandatory. The arguments passed to a function can be retrieved from an array-like |
In the example, the test_prime
function has only one argument: the candidate
argument, which is the prime number candidate to be tested. Function arguments work like variables, but their values are assigned by the statement calling the function. For example, the statement test_prime(231)
will call the test_prime
function and assign the value 231 to the candidate
argument, which will then be available inside the function’s body like an ordinary variable.
If the calling statement uses simple variables for the function’s parameters, their values will be copied to the function arguments. This procedure — copying the values of the parameters used in the calling statement to the parameters used inside the function —- is called passing arguments by value. Any modifications made to the argument by the function does not affect the original variable used in the calling statement. However, if the calling statement uses complex objects as arguments (that is, an object with properties and methods attached to it) for the function’s parameters, they will be passed as reference and the function will be able to modify the original object used in the calling statement.
The arguments that are passed by value, as well as the variables declared within the function, are not visible outside it. That is, their scope is restricted to the body of the function where they were declared. Nonetheless, functions are usually employed to create some output visible outside the function. To share a value with its calling function, a function defines a return
statement.
For instance, the test_prime
function in the previous example returns the value of the is_prime
variable. Therefore, the function can replace the variable anywhere it would be used in the original example:
// The number we want to evaluate
let candidate = 231;
// Display the result in the console window
if ( test_prime(candidate) )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
The return
statement, as its name indicates, returns control to the calling function. Therefore, wherever the return
statement is placed in the function, nothing following it is executed. A function can contain multiple return
statements. This practice can be useful if some are within conditional blocks of statements, so that the function might or might not execute a particular return
statement on each run.
Some functions may not return a value, so the return
statement is not mandatory. The function’s internal statements are executed regardless of its presence, so functions can also be used to change the values of global variables or the contents of objects passed by reference, for example. Notwithstanding, if the function does not have a return
statement, its default return value is set to undefined
: a reserved variable that does not have a value and cannot be written.
Function Expressions
In JavaScript, functions are just another type of object. Thus, functions can be employed in the script like variables. This characteristic becomes explicit when the function is declared using an alternative syntax, called function expressions:
let test_prime = function(candidate)
{
// Auxiliary variable
let is_prime = true;
// Start with the lowest prime number after 1
let factor = 2;
// Keeps evaluating while factor is less than the candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
// Send the answer back
return is_prime;
}
The only difference between this example and the function declaration in the previous example is in the first line: let test_prime = function(candidate)
instead of function test_prime(candidate)
. In a function expression, the test_prime
name is used for the object containing the function and not to name the function itself. Functions defined in function expressions are called in the same way as functions defined using the declaration syntax. However, whereas declared functions can be called before or after their declaration, function expressions can be called only after their initialization. Like with variables, calling a function defined in an expression before its initialization will cause a reference error.
Function Recursion
In addition to executing statements and calling builtin functions, custom functions can also call other custom functions, including themselves. To call a function from itself is called function recursion. Depending on the type of problem you are trying to solve, using recursive functions can be more straightforward than using nested loops to perform repetitive tasks.
So far, we know how to use a function to test whether a given number is prime. Now suppose you want to find the next prime following a given number. You could employ a while
loop to increment the candidate number and write a nested loop which will look for integer factors for that candidate:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "true" keeps the loop going until a prime is found
while ( true )
{
// Auxiliary control variable
let is_prime = true;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Test the next candidate
is_prime = false;
break;
}
}
// End loop and return candidate if it is prime
if ( is_prime )
{
return candidate;
}
// If prime not found yet, try the next odd number
candidate = candidate + 2;
}
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Note that we need to use a constant condition for the while
loop (the true
expression inside the parenthesis) and the auxiliary variable is_prime
to know when to stop the loop. Athough this solution is correct, using nested loops is not as elegant as using recursion to perform the same task:
// This function returns the next prime number
// after the number given as its only argument
function next_prime(from)
{
// We are only interested in the positive primes,
// so we will consider the number 2 as the next
// prime after any number less than two.
if ( from < 2 )
{
return 2;
}
// The number 2 is the only even positive prime,
// so it will be easier to treat it separately.
if ( from == 2 )
{
return 3;
}
// Decrement "from" if it is an even number
if ( from % 2 == 0 )
{
from--;
}
// Start searching for primes greater then 3.
// The prime candidate is the next odd number
let candidate = from + 2;
// "candidate" is an odd number, so the loop will
// try only the odd factors, starting with 3
for ( let factor = 3; factor < candidate; factor = factor + 2 )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime.
// Call the next_prime function recursively, this time
// using the failed candidate as the argument.
return next_prime(candidate);
}
}
// "candidate" is not divisible by any integer factor other
// than 1 and itself, therefore it is a prime number.
return candidate;
}
let from = 1024;
console.log("The next prime after", from, "is", next_prime(from));
Both versions of next_prime
return the next prime number after the number given as its only argument (from
). The recursive version, like the previous version, starts by checking the special cases (i.e. numbers less or equal to two). Then it increments the candidate and starts looking for any integer factors with the for
loop (notice that the while
loop is not there anymore). At that point, the only even prime number has already been tested, so the candidate and its possible factors are incremented by two. (An odd number plus two is the next odd number.)
There are only two ways out of the for
loop in the example. If all the possible factors are tested and none of them has a remainder equal to zero when dividing the candidate, the for
loop completes and the function returns the candidate as the next prime number after from
. Otherwise, if factor
is an integer factor of candidate
(candidate % factor == 0
), the returned value comes from the next_prime
function called recursively, this time with the incremented candidate
as its from
parameter. The calls for next_prime
will be stacked on top of one another, until one candidate finally turns up no integer factors. Then the last next_prime
instance containing the prime number will return it to the previous next_prime
instance, and thus successively down to the first next_prime
instance. Even though each invocation of the function uses the same names for variables, the invocations are isolated from each other, so their variables are kept separated in memory.
Guided Exercises
-
What kind of overhead can developers mitigate by using functions?
-
What is the difference between function arguments passed by value and function arguments passed by reference?
-
Which value will be used as the output of a custom function if it does not have a return statement?
Explorational Exercises
-
What is the probable cause of an Uncaught Reference Error issued when calling a function declared with the expression syntax?
-
Write a function called
multiples_of
that receives three arguments:factor
,from
, andto
. Inside the function, use theconsole.log()
instruction to print all multiples offactor
lying betweenfrom
andto
.
Summary
This lesson covers how to write custom functions in JavaScript code. Custom functions let the developer divide the application in “chunks” of reusable code, making it easier to write and maintain larger programs. The lesson goes through the following concepts and procedures:
-
How to define a custom function: function declarations and function expressions.
-
Using parameters as the function input.
-
Using the
return
statement to set the function output. -
Function recursion.
Answers to Guided Exercises
-
What kind of overhead can developers mitigate by using functions?
Functions allow us to reuse code, which facilitates code maintenance. A smaller script file also saves memory and download time.
-
What is the difference between function arguments passed by value and function arguments passed by reference?
When passed by value, the argument is copied to the function and the function is not able to modify the original variable in the calling statement. When passed by reference, the function is able to manipulate the original variable used in the calling statement.
-
Which value will be used as the output of a custom function if it does not have a return statement?
The returned value will be set to
undefined
.
Answers to Explorational Exercises
-
What is the probable cause of an Uncaught Reference Error issued when calling a function declared with the expression syntax?
The function was called before its declaration in the script file.
-
Write a function called
multiples_of
that receives three arguments:factor
,from
, andto
. Inside the function, use theconsole.log()
instruction to print all multiples offactor
lying betweenfrom
andto
.function multiples_of(factor, from, to) { for ( let number = from; number <= to; number++ ) { if ( number % factor == 0 ) { console.log(factor, "*", number / factor, "=", number); } } }