In Javascript, a function is a named sequence of statements that belong together. Their primary purpose is to help us organize programs into chunks that match how we think about the problem.
The syntax for a function definition is:
function NAME( PARAMETERS ) {
STATEMENTS
}
We can make up any names we want for the functions we create, except that we can't use a name that is a Javascript keyword, and the names must follow the rules for legal identifiers (the same rules that apply to variable names).
There can be any number of statements inside the function, but they have to be
between the curly braces ({}
). These statements make up the function body.
In the examples in this book, we will use the
standard indentation of two spaces. Function definitions are the second of
several compound statements we will see, all of which have the same
pattern:
- A header line which begins with a keyword and ends with an opening (left) curly brace.
- A body consisting of one or more Javascript statements, each indented the same amount — we will use 2 spaces — from the header line.
- A closing (right) curly brace.
We've already seen the for
loop which follows this pattern.
So looking again at the function definition, the keyword in the header is function
, which is
followed by the name of the function and some parameters enclosed in
parentheses. The parameter list may be empty, or it may contain any number of
parameters separated from one another by commas. In either case, the parentheses are required.
The parameters specifies what information, if any, we have to provide in order to use the new function.
Suppose we are writing a program to calculate the amount of tip due on a bill. We might write a function to "calculate tip". "calculate tip" is an abstraction, or a mental chunk, of a number of smaller steps. So let's write a function to capture the pattern of this "building block":
/*
Calculate the tip on a bill, given the pct of the tip.
*/
function calculateTip (bill, pct) {
let tip = bill * (pct * .01); // convert pct to a decimal and calculate
tip = tip.toFixed(tip, 2); // convert tip to a number with 2 decimal places
total = tip + bill;
total = Number.parseFloat(total).toFixed(2);
// now show the results to the user
console.log("Bill amount: $" + bill);
console.log("Tip percentage: " + pct + "%");
console.log("Total amount due: $" + total);
}
// find the amount of an 18% tip on a $100 bill
calculateTip(100,18);
This function is named calculateTip
. It has two parameters: one to tell
the function the amount of the bill, and the other to tell it the percent
tip to calculate.
Defining a new function does not make the function run. To do that we
need a function call. We've already seen how to call some built-in
functions like console.log, window.input, and Number.parseInt. Function
calls contain the name of the function being executed followed by a list
of values, called arguments, which are assigned to the parameters in
the function definition. So in the last line of the example program
above, we call the function, and pass 100
as the amount of the bill
and 18
as the percentage of the tip. While the function is
executing, then, the variable bill
refers to the value 100, and the
variable pct
refers to 18. We can pass either variables (like myBill
)
or literal values (like 100
) as arguments.
Once we've defined a function, we can call it as often as we like, and its
statements will be executed each time we call it. In the next example, we calculate 3 different
tip amounts for the same bill, using calculateTip
defined above.
let myBill = 100;
calculateTip(myBill, 15);
calculateTip(myBill, 18);
calculateTip(myBill, 20);
Coding Style
Now that you are about to write longer, more complex pieces of Javascript, it is a good time to talk about coding style. Most languages can be written (or more concise, formatted) in different styles; some are more readable than others. Making it easy for others to read your code is always a good idea, and adopting a nice coding style helps tremendously for that.
For our Javascript, we will follow the coding style described in the Mozilla project developer's guide. Here are the most important points extracted for you:
- Use 2-space indentation, and no tabs.
- Wrap lines so that they don’t exceed 80 characters.
- This helps users with small displays and makes it possible to have several code files side-by-side on larger displays.
- Use double quotes
"
for string literals, except when avoiding inline double quotes - Use blank lines to separate functions and classes, and larger blocks of code inside functions.
- When possible, put comments on a line of their own.
- Use spaces around operators and after commas, but not directly inside bracketing constructs: a = f(1, 2) + g(3, 4).
- Name your variables, classes, and functions consistently; the convention is to use CamelCase for identifier names.
- Don’t use fancy encodings if your code is meant to be used in
international environments. Use UTF-8 or plain ASCII.
- Likewise, don’t use non-ASCII characters in identifiers.
So far, we have looked at the elements of a program—variables
,
expressions
, and statements
—in isolation, without talking about how to
combine them.
One of the most useful features of programming languages is their
ability to take small building blocks and compose them. In our calculateTip
example, we call several Javascript built-in functions: toFixed
to keep our amounts
to 2 decimal places and Number.parseFloat
to convert the data to a float so that
we can use toFixed
. We use console.log
to print our output on the Javascript
console. As we will see, we can compose our programs of many functions
that we define ourselves.
There are some points worth noting here:
- Functions can call other functions.
- A caller of this function might say
calculateTip(myBill, 15)
. The parameters of this function,bill
andtip
, are assigned the values of the myBill variable, and the number literal 15, respectively. - In the body of the function they are just like any other variable.
So far, it may not be clear why it is worth the trouble to create all of these new functions. Actually, there are a lot of reasons, but this example demonstrates two:
- Creating a new function gives us an opportunity to name a group of statements. Functions can simplify a program by hiding a complex computation behind a single command. The function (including its name) can capture our mental chunking, or abstraction, of the problem.
- Creating a new function can make a program smaller by eliminating repetitive code.
As we might expect, we have to create a function before we can execute it. In other words, the function definition has to be executed before the function is called.
In order to ensure that a function is defined before its first use, we have to know the order in which statements are executed, which is called the flow of execution.
Execution always begins at the first statement of the program. Statements are executed one at a time, in order from top to bottom.
Function definitions do not alter the flow of execution of the program, but remember that statements inside the function are not executed until the function is called. In Javascript we can define one function inside another. In this case, the inner definition isn't executed until the outer function is called.
Function calls are like a detour in the flow of execution. Instead of going to the next statement, the flow jumps to the first line of the called function, executes all the statements there, and then comes back to pick up where it left off.
That sounds simple enough, until we remember that one function can call another. While in the middle of one function, the program might have to execute the statements in another function. But while executing that new function, the program might have to execute yet another function!
Fortunately, Javascript is adept at keeping track of where it is, so each time a function completes, the program picks up where it left off in the function that called it. When it gets to the end of the program, it terminates.
What's the moral of this sordid tale? When we read a program, don't read from top to bottom. Instead, follow the flow of execution.
Most functions require arguments: the arguments provide for generalization, allowing the same
function to work with different data inputs.
For example, if we want to find the absolute value of a number, we have
to indicate what the number is. The Javascript Math
class has a built-in function for
computing the absolute value:
⠕ Math.abs(5);
=> 5
⠕ Math.abs(-5);
=> 5
In this example, the arguments to the abs
function are 5 and -5.
Some functions take more than one argument. For example the function calculateTip
function we wrote in the example above takes two arguments:
the amount of the bill and the percent tip to calculate.
Inside the function, the values that are
passed get assigned to variables called parameters.
⠕ calculateTip(87, 10);
Bill amount: $87
Tip percentage: 10%
Total amount due: $8.70
Another built-in function that takes more than one argument is Math.max
.
⠕ Math.max(7, 11);
=> 11
⠕ Math.max(4, 1, 17, 2, 12);
=> 17
⠕ Math.max(3 * 11, 5**3, 512 - 9, 1024**0);
=> 503
Math.max
can be passed any number of arguments, separated by commas, and will
return the largest value passed. The arguments can be either simple values or
expressions. In the last example, 503 is returned, since it is larger than 33,
125, and 1. All of the expressions are resolved --- in this case the mathematical
operations are calculated --- before their values are assigned to the function
parameters.
Some functions return values. In the previous section we saw that Math.abs
and Math.max
return values. calculateTip
does not return a value; it
uses console.log
to produce output on the screen for the user.
We can use the return values from functions to compose more complex functions.
A function that returns a value is called a fruitful function in this book.
The opposite of a fruitful function is void function — one that is not executed
for its resulting value, but is executed because it does something useful.
(Languages like Java, C#, C and C++ use the term "void function", other languages like Pascal
call it a procedure.) Even though void functions are not executed
for their resulting value, Javascript always wants to return something. So if the programmer
doesn't arrange to return a value, Javascript will automatically return the value undefined
.
How do we write our own fruitful function? Let's look at the standard formula for compound interest as an example of a fruitful function:
/*
Apply the compound interest formula to p
to produce the final amount.
*/
function finalAmt (p, r, n, t) {
let a = p * (1 + r/n) ** (n * t);
// This is new, and makes the function fruitful
return a;
}
//now that we have the function above, let's call it
let toInvest = Number.parseFloat( window.prompt("How much do you want to invest?") );
let fnl = finalAmt(toInvest, 0.08, 12, 5);
console.log("At the end of the period you'll have", fnl);
- The return statement is followed an expression (
a
in this case). This expression will be evaluated and returned to the caller as the "fruit" of calling this function. - We prompted the user for the principal amount. The type of
toInvest
is a string, but we need a number before we can work with it. Javascript can automatically convert it to a number for us when we use it in the calculation. Because it is money, and could have decimal places, we've used theNumber.parseFloat
type converter function to parse the string and return a float. - Notice how we entered the arguments for 8% interest, compounded 12 times per year, for 5 years.
- When we run this, if we enter
100
when prompted for the amount to invest, we get the output
At the end of the period you'll have 148.9845708301606
This is a bit messy with all these decimal places, but remember that Javascript doesn't understand that we're working with money: it just does the calculation to the best of its ability, without rounding. Later we'll see how to format the string that is printed in such a way that it does get nicely rounded to two decimal places before printing. - The line
let toInvest = Number.parseFloat( window.prompt("How much do you want to invest?") );
also shows yet another example of composition — we can call a function likeNumber.parseFloat
, and its arguments can be the results of other function calls (likewindow.prompt
) that we've called along the way.
Notice something else very important here. The name of the variable we pass as an
argument — toInvest
— has nothing to do with the name of the parameter
— p
. It is as if p = toInvest
is executed when finalAmt
is called.
It doesn't matter what the value was named in
the caller, in finalAmt
its name is p
.
These short variable names are getting quite tricky, so perhaps we'd prefer one of these versions instead:
function finalAmtV2(principalAmount, nominalPercentageRate, numTimesPerYear, years) {
let a = principalAmount * (1 + nominalPercentageRate / numTimesPerYear) ** (numTimesPerYear * years);
return a;
}
function finalAmtV3(amt, rate, compounded, years) {
let a = amt * (1 + rate / compounded) ** (compounded * years);
return a;
}
They all do the same thing. Use your judgment to write code that can be best
understood by other humans!
Short variable names are more economical and sometimes make
code easier to read:
E = mc2 would not be nearly so memorable if Einstein had
used longer variable names! If you do prefer short names,
make sure you also have some comments to enlighten the reader
about what the variables are used for.
When we declare a new local variable inside a function, it only exists inside the function, and we cannot use it outside. For example, consider again this function:
function finalAmt (p, r, n, t) {
let a = p * (1 + r/n) ** (n * t);
return a;
}
If we try to use a
, outside the function, we'll get an error like this:
⠕ a
ReferenceError: a is not defined
The variable a
is local to finalAmt
, and is not visible
outside the function.
Additionally, a
only exists while the function is being executed —
we call this its lifetime.
When the execution of the function terminates, the local variables are destroyed.
Parameters are also local, and act like local variables.
For example, the lifetimes of p
, r
, n
, t
begin when finalAmt
is called,
and the lifetime ends when the function completes its execution.
So it is not possible for a function to set some local variable to a value, complete its execution, and then when it is called again next time, recover the local variable. Each call of the function creates new local variables, and their lifetimes expire when the function returns to the caller.
argument
: A value provided to a function when the function is called. This value is assigned to the corresponding parameter in the function. The argument can be the result of an expression which may involve operators, operands and calls to other fruitful functions.
body
: The second part of a compound statement. The body consists of a sequence of statements all indented the same amount from the beginning of the header. The standard amount of indentation used within the Python community is 4 spaces.
compound statement
: A statement that consists of two parts:
1. header - which begins with a keyword determining the statement
type, and ends with a colon.
2. body - containing one or more statements indented the same amount
from the header.
The syntax of a compound statement looks like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.javascript}
keyword ... :
statement
statement ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
flow of execution
: The order in which statements are executed during a program run.
frame
: A box in a stack diagram that represents a function call. It contains the local variables and parameters of the function.
function
: A named sequence of statements that performs some useful operation. Functions may or may not take parameters and may or may not produce a result.
function call
: A statement that executes a function. It consists of the name of the function followed by a list of arguments enclosed in parentheses.
function composition
: Using the output from one function call as the input to another.
function definition
: A statement that creates a new function, specifying its name, parameters, and the statements it executes.
fruitful function
: A function that returns a value when it is called.
header line
: The first part of a compound statement. A header line begins with a keyword and ends with a colon (:)
import statement
: A statement which permits functions and variables defined in another Python module to be brought into the environment of another script. To use the features of the turtle, we need to first import the turtle module.
lifetime
: Variables and objects have lifetimes — they are created at some point during program execution, and will be destroyed at some time.
local variable
: A variable defined inside a function. A local variable can only be used inside its function. Parameters of a function are also a special kind of local variable.
parameter
: A name used inside a function to refer to the value which was passed to it as an argument.
refactor
: A fancy word to describe reorganizing our program code, usually to make it more understandable. Typically, we have a program that is already working, then we go back to "tidy it up". It often involves choosing better variable names, or spotting repeated patterns and moving that code into a function.
stack diagram
: A graphical representation of a stack of functions, their variables, and the values to which they refer.
traceback
: A list of the functions that are executing, printed when a runtime error occurs. A traceback is also commonly referred to as a stack trace, since it lists the functions in the order in which they are stored in the runtime stack.
void function
: The opposite of a fruitful function: one that does not return a value. It is executed for the work it does, rather than for the value it returns.
-
Write a void (non-fruitful) function to that prints out a "hello" message. Your function should declare 3 parameters:
firstName
,lastName
, andtitle
.title
will be Mr., Ms., Dr., etc. The function should print a message like this one:Hello Dr. Matthew Curinga.
-
Write a function
half(num)
which returns the value ofnum
divided by 2. -
Write a function
triple(num)
which returnnum
* 3. -
Write a function
areaOfACircle(r)
which returns the area of a circle of radiusr
. For the value of PI, use the constantMath.PI
(Hint: if you can't remember how to find the area of a circle, look it up or ask a friend.) -
Write a function
hypotenuse(a, b)
which calculates the hypotenuse of a right triangle when given the length of sidesa
andb
. Use the Pythagorean theorema^2 + b^2 = c^2
.
Note, you will need to be able to calculate square roots to solve this problem. You can use the build in math functionMath.sqrt
. -
(hard bonus) Write a function called
distance(x1, y1, x2, y2)
which calculates the distance between the point at (x1, y1) and (x2, y2) on a Cartesian Plane. You can find the formula for this at http://www.mathsisfun.com/algebra/distance-2-points.html
Use thehypotenuse
function from exercise 5 to compose this function.
This is the first "lab" in our textbook. For this project, you will be asked to write
a complete program that solves a problem. The labs should break up parts of the program
into different functions. Your program should start when the main()
function is
called. main
is not a keyword in Javascript, but in many programming languages there
is a convention that the starting function is called main
. Please take a look at the
Tip Calculator case study below for a sense of how your program should be structured.
For this first lab, too, please pay attention to your coding style to make sure that the program is well formatted and easy for human readers to understand.
For this lab you are going to revisit the lemonade stand estimator from the exercises in chapter 2. You will make an interactive program that asks the user to enter all of the data for:
- prices of the ingredients
- prices for the materials (e.g. cups)
- sale price for a cup of lemonade
- and estimation of the number of cups sold
Once all of the data is entered, the program will print out a neatly formatted message with the results of the estimation.