In this reading, we'll learn a very powerful aspect of programming languages, namely the ability to define and use our own functions. We have used many "built-in" functions, such as alert and parseInt; now we will be able to build our own.
alert
parseInt
Why is this so important? Consider a function like parseInt: it does something that is useful in many situations and we can use it without understanding how it works (its implementation). If we are involved in a large software project, someone else on our team can implement a function that we can use, and vice versa. We can use their code without understanding all the details of its implementation, which saves us mental effort. This idea also allows the team to be more efficient than if each person had to understand everything.
This is another manifestation of the idea of modularity. A function is a kind of module. It can be defined in one place and used in many places, just as a single CSS rule can be defined in one place and used in many places. Furthermore, if we discover a bug in the function, its definition can be fixed and every user of that code gets the improvement. This is similar to modules in the real world, where if you drop your headphones into the bathtub and short them out, you can replace them without having to replace your music player, too.
So far, this may seem pretty intuitive; I certainly hope so. Nevertheless, functions can be pretty complex at times. To explain them, I will use three metaphors:
We can even combine all these metaphors and imagine a function as kitchen with a chef inside. The users of the kitchen don't need to know anything about the workings of the kitchen. All they need to know is that they give it certain inputs (eggs, flour, cocoa, or maybe just money) and outputs are generated (a chocolate cake).
Defining a function is like making up a recipe. We have to give it a name and list the steps that need to be done. Once the recipe is defined, anyone can use it.
Let's look more precisely at the syntax of a function definition:
<script> function functionName() { // comment about your lovely function . . JavaScript code to define the function goes here . . }
To define your own function you need:
It is also considered good practice to put a comment within the function describing what the parameters are for, what value gets computed if any, what actions are performed, etc. Anything that isn't obvious from the code. (Comments that just repeat the code in English are bad.)
Here's an example:
function chocolateCake() { // step 1, combine sugar and flour in a large bowl // step 2, add two large eggs // ... // frost and serve }
The chocolate cake example is a little silly, because we don't know any JavaScript for the definition. Here's a better one that formats the date on the page. You'll recognize this code from our recent discussion of the Date object, though I've abbreviated the code a little.
function formatDate() { // create a date object, representing this moment, and store it var dateObj = new Date(); // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; // Add 1 because Jan is 0, etc. var the_year = dateObj.getFullYear(); var current_date = the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById("date2").innerHTML = current_date; }
Now we come to an interesting situation, because we have defined a function, but how do we use it. Here, I'll use the metaphor of a machine: we have a blender, but how do we turn it on?
Computer scientists have a special word for running a function (making it do its job), and that is invocation. (Like invoking the gods.) We say that we invoke the formatDate function to get it to run and insert the date onto the page. In less formal circumstances, we say that we call the function. (This is like call a friend for help.)
invoke
formatDate
call
call a friend for help
You've been doing this all along, calling or invoking the built-in functions. Here are some examples:
var name = prompt("what's your name?"); // invoke prompt var now = new Date(); // invoke Date formatDate(); // invoke formatDate
Invoking our formatDate function is no different from invoking the built-in functions. True, those built-in functions have arguments, which makes the invocation look a little different, so we'll turn to that in a moment.
Your code can define lots of functions, maybe in several external .js files so that the definitions can be shared by many files in your website. None of those functions need ever be invoked, in which case they will be useless, like those recipes and kitchen gadgets you never use and which just collect dust. It's important to keep in mind the difference between definition and invocation of a function.
The function above was fairly inflexible: it only formatted today's date and it always inserted the date into the date2 element on the page. Imagine a kitchen gadget that could chop up ice but nothing else. If you want to chop carrots, you have to buy a different machine. To chop celery, yet another machine. A more flexible and general function would allow you to input the date to be formatted, and the ID of the element you want to insert the date into. For that, we have to use a definition syntax that specifies parameters. We will also have to change the invocation of the function to supply these associated arguments.
date2
Here's our modified function. Note that I've changed the name to formatDate2 only so that it's clear what version of the function we are discussing. I could have used the same name as the first version, or I could have named it something different, like formatDate_version_2 or even fred.
formatDate2
formatDate_version_2
fred
function formatDate2(dateObj, targetId) { // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; // Add 1 because Jan is 0, etc. var the_year = dateObj.getFullYear(); var current_date = the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById(targetId).innerHTML = current_date; }
Now, to get the same effect as we had before, namely to format the current date into date2, we would invoke the function like this:
formatDate2( new Date(), "date2" );
So far, this seems less powerful than what we had before. In fact, let's compare them side by side:
function formatDate() { var dateObj = new Date(); // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; var the_year = dateObj.getFullYear(); var current_date = the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById("date2").innerHTML = current_date; }
function formatDate2(dateObj, targetId) { // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; var the_year = dateObj.getFullYear(); var current_date = the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById(targetId).innerHTML = current_date; }
formatDate();
formatDate2(new Date(), "date2");
???
formatDate2(new Date("10/31/2019"), "halloween");
If you look at the original invocation, it looks like some of the code has just migrated out of the function definition and become part of the invocation, so it's now harder to use the function. We've transferred work from the implementation to the caller.
This is true. But this burden is useful, because it allows us to use the function in other situations, such as formatting the date of Halloween in another place on the page. You can see that that's impossible with the original version.
Notice, by the way, in that side-by-side comparison, that we are able to make the function more flexible by replacing particular parts of the definition with variables and then those variables are placed between the parentheses in the first line of the function definition:
dateObj
new Date()
targetId
This is a fairly common trick: making something more flexible by replacing constants with variables, and supplying values for those variables when the function is invoked.
The names in the parentheses in the function definition are called parameters, and the values that are supplied when the function is invoked are called arguments. (This terminology is not standardize in computer science, but it's common and we will use it.) The parameter is a kind of variable, in that it's a storage location; it stores the corresponding argument.
In the examples above, we had:
"date2"
new Date("10/31/2019")
"halloween"
If you like, you can think of the beginning of the function invocation as a sequence of assignment statements, assigning the value of the each argument, one at a time, to the corresponding parameter.
If arguments are like inputs to a machine, can we also have outputs from our machines? Yes, a function in JavaScript can have a return value. One slight restriction is that a JavaScript function can have any number of distinct inputs (arguments), but it can only have one return value, just like you can put any number of ingredients into your blender, but you only get one output.
Let's define another function. This one will map day numbers (0 through 6) to the corresponding English name. If we assume that the day number is in a variable called dayNum and we want to compute the result in a variable called dayName, we might write code like this:
dayNum
dayName
var dayName = "unknown"; if( dayNum == 0 ) { dayName = "Sunday"; } else if ( dayNum == 1 ) { dayName = "Monday"; } else if ( dayNum == 2 ) { dayName = "Tuesday"; } else if ( dayNum == 3 ) { dayName = "Wednesday"; } else if ( dayNum == 4 ) { dayName = "Thursday"; } else if ( dayNum == 5 ) { dayName = "Friday"; } else if ( dayNum == 6 ) { dayName = "Saturday"; }
That code just uses our skills in cascading if statements. Now, we need to package that code up into a function, a little machine that will take in a number and give us back a string. By take in, we mean the function takes an input in the form of an argument. Here's what we might write:
cascading if
take in
function dayNameEnglish(dayNum) { var dayName = "unknown"; if( dayNum == 0 ) { dayName = "Sunday"; } else if ( dayNum == 1 ) { dayName = "Monday"; } else if ( dayNum == 2 ) { dayName = "Tuesday"; } else if ( dayNum == 3 ) { dayName = "Wednesday"; } else if ( dayNum == 4 ) { dayName = "Thursday"; } else if ( dayNum == 5 ) { dayName = "Friday"; } else if ( dayNum == 6 ) { dayName = "Saturday"; } return dayName; }
Here's an example of the function in action (that is, an invocation of the function):
var name = dayNameEnglish(0); // should be "Sunday" alert(name);
Again, we refer to the 0 in the invocation as the argument. Inside the machine, the parameter dayNum will have the value 0.
At the end of the function is something new: return dayName. That code says that the value of the function is whatever is in that variable.
return dayName
Armed with this new function, we can re-write our date formatting function as follows, now named formatDate3
formatDate3
function formatDate3(dateObj, targetId) { // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; // Add 1 because Jan is 0, etc. var the_year = dateObj.getFullYear(); var dayName = dayNameEnglish( dateObj.getDay() ); // e.g. "Friday" var current_date = dayName + ", "+ the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById(targetId).innerHTML = current_date; }
So that you can test this, we've created the following element:
Note how the formatDate3 function invokes the dayNameEnglish function:
dayNameEnglish
var dayString = dayNameEnglish( dateObj.getDay() ); // e.g. "Friday" var current_date = dayString + ", "+ the_month + "/" + the_date + "/" + the_year;
This code is very abstract! The dateObj variable contains an object that somebody wants formatted (whoever invoked the formatDate3 function). We use a method, .getDay() to extract the number for the day of the week. That number is then sent as an argument to the dayNameEnglish() function. That function runs and returns a string, which we store in dayString and use on the next line.
.getDay()
dayNameEnglish()
dayString
When we are programming and defining functions, we often are in a situation where we don't know the particulars. We are trying to create a general solution. We don't know what date is being formatted or where it's going to go on the page. We have to trust that the caller knows what they are doing. In a way, this is like the chef in the kitchen who gets an order for a chocolate cream pie. The chef doesn't know whether the pie will be for dessert or used as a weapon, but that's not their concern. Their job is just to make the pie.
You probably noticed in that last example that we had two functions that we defined, one of which invoked the other. That often happens, and you might think that the computer could get confused about what line of code should be executed when. But, it's actually pretty intuitive.
This can be nested as deeply as you like (theoretically infinite, albeit not infinitely deep in practice) and the computer never gets confused, even when different functions are invoking dayNameEnglish. It always knows who and where to return to.
Throughout our examples, we've never been reluctant to create new variables inside our functions when we wanted them, such as the_date and the_month. This is perfectly legal and desirable. These are called local variables, because they only exist inside the function. They are created when the function is invoked, and they cease to exist when the function is finished.
the_date
the_month
If you had very sharp eyes, you might have noticed that our two functions had a local variable that had the same name, namely dayName. That is also legal.
To explain what's going on, I appeal to our third metaphor of functions, as separate work spaces, like an enclosed kitchen. Kitchens have lots of storage containers that are not inputs or outputs --- things like mixing bowls and measuring cups. Different kitchens have different storage containers, even if they are called the same thing. Here's an abstract example:
function fred() { var snackbox = "puking pastille"; alert(snackbox); ... } function george() { var snackbox = "nosebleed nougat"; alert(snackbox); ... }
Here we have two functions named after Weasley twins in Harry Potter. Each function has a local variable called snackbox, but these are completely different storage locations. Think of them as being in different kitchens, each presided over by a different Weasley twin. The local variables come into existence when the function is invoked and disappear when it finishes. (Easy cleanup!)
snackbox
Local variables are distinguished from global variables which are shared by everything. The following example may help. Read the code, then run it, and see if you can make sense of the behavior.
You saw the following sequence of output:
The amazing thing is that all three of these variables are separate storage locations, so none of the assignments affects any of the others. There's a "snackbox" in Fred's private area; another, different, one in George's private area, and a third one in the common room that everyone shares.
Usually, that's exactly what you want. Occasionally, a function wants to modify the global variable rather than a local variable. To do so, just omit the var before the variable name:
var
This time, you saw the following sequence of output:
The best practice is always to use local variables, creating them with var, and only to use global variables when necessary.
A bit of terminology: the places that a variable is visible or usable is called its scope. We might say that some variables have scope that is local while others are global.
Earlier, we noticed that the improved version of date formatting, namely formatDate2, resulted in more work for the caller:
In both of these, the first argument is an expression that involves the Date function and so on. As programmers, we can make the caller's job easier with just a bit of work. Here's what we would like to say instead:
Date
formatDate2b("today", "date2");
formatDate2b("10/31/2019", "halloween");
That's much clearer and easier, and just involves a little bit of work for the implementation. Here's one way, with the conversion of the dateSpec parameter (date specification) into the desired kind of Date object. This happens in the lines 2--6 of the function, and everything else is the same.
dateSpec
function formatDate2b(dateSpec, targetId) { if( dateSpec == "today" ) { var dateObj = new Date(); } else { var dateObj = new Date(dateSpec); } // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; var the_year = dateObj.getFullYear(); var current_date = the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById(targetId).innerHTML = current_date; }
The point here is that these interfaces between functions and other bits of software are designed by humans, and they can be easier and more convenient if we want them to be. This is part of creating an Application Programmer Interface (API). An API is a term for a set of software interfaces, such as functions and their arguments, that allow an application programmer to get something done.
At this point, you know most of what you need to know about functions for this semester, but one mind-blowing concept remains. Programming languages describe certain values as being first class objects when you can do normal things with them like store them in variables and pass them as arguments to a function. In JavaScript, as you know, numbers are first-class objects, and so are strings and even booleans. Date objects are first-class.
first class
The mind-blowing thing is that functions are first-class objects! That means we can store them in variables and pass them as arguments to a function. For example, we can do the following:
var dayNamer = dayNameEnglish;
If the function is stored in a variable, can we still invoke it? Yes! It turns out that the parentheses in a function invocation are sort-of like the on button of a machine. Here's how we could invoke that function above:
on
dayNamer( 0 ); // Sunday
So what? Imagine we define another day naming function:
function dayNameFrench(dayNum) { var dayName = "unknown"; if( dayNum == 0 ) { dayName = "Dimanche"; } else if ( dayNum == 1 ) { dayName = "Lundi"; } else if ( dayNum == 2 ) { dayName = "Mardi"; } else if ( dayNum == 3 ) { dayName = "Mecredie"; } else if ( dayNum == 4 ) { dayName = "Jeudi"; } else if ( dayNum == 5 ) { dayName = "Vendredi"; } else if ( dayNum == 6 ) { dayName = "Samedi"; } return dayName; }
Our date-formatting function seems to be always using the English days of the week. Could it be made even more flexible, where we supply a function that returns the correct day name? Yes, we can. Here's the latest version, now named formatDate4:
formatDate4
function formatDate4(dateObj, dayNamer, targetId) { // format info about the day var the_date = dateObj.getDate(); var the_month = dateObj.getMonth() + 1; // Add 1 because Jan is 0, etc. var the_year = dateObj.getFullYear(); var dayName = dayNamer( dateObj.getDay() ); // e.g. "Friday" or "Vendredi" var current_date = dayName + ", "+ the_month + "/" + the_date + "/" + the_year; // insert the formatted string into the document. document.getElementById(targetId).innerHTML = current_date; }
So, the dayNameEnglish function on line 6 has now become a variable, a parameter whose value will be supplied when the function is invoked.
Here's the key thing to notice and remember. When we have dayNameFrench in the invocation, we don't use parentheses. That's because we are not invoking it right now, but we are passing it (the dayNameFrench function) into the formatDate4 function. This is like handing someone a blender or other kitchen gadget instead of the output of the gadget.
dayNameFrench
This may seem like an esoteric usage, and it is. We won't be asking you to define functions as sophisticated as formatDate4. However, we will expect you to understand the difference between invoking a function, where the parentheses are used, and passing it as an argument to another function, where the parentheses are omitted.
We covered a lot of difficult ground in this reading. The main points are: