We have worked with many pre-defined JavaScript/jQuery methods and functions. Some examples are:
alert()
prompt()
parseFloat()
today.getYear()
, /* where "today" is a variable
containing a Date object */
$("sel").text()
$("sel").css()
A function is a named chunk of JavaScript code.
A method is a function associated with some object. In the
examples above, we used a method associated with a date object in a
variable called today
and two methods associated with a
jQuery selection (an object that represents a set of page elements that
match the given selection expression, "sel").
(Information you don't need to know: The
functions alert()
and prompt()
are actually
methods of the window object stored in the variable window
— JavaScript will fill in
window.
automatically, but you can write
window.prompt()
if you want.)
When a function's name is used with parentheses (containing arguments, if any), the code associated with that name is executed. This is called invoking or calling the function.
Today we'll learn why we would want to define our own functions, how to define functions, and how to use functions.
Suppose we wanted to put the lyrics of the old nursery rhyme The Farmer in the Dell into our web page. It would make sense just to type the lyrics into our HTML, but we're not going to do that. Instead, we're going to generate the text, just as we did with our madlibs. Bear with us, this is going someplace. Here is the first version of our code (v1):
(FYI, the jQuery append
method adds the
contents to the selected page elements.)
Obviously, there's a lot of repetition in here. Not as bad
as 99 bottles of beer on the wall
but close. It's a song based
on a pattern, and one fundamental purpose of functions is to
capture patterns.
Note also the nonsense phrase Hi-ho, the derry-o
in the middle.
It turns out that varies from area to area. In London, they say Ee-i,
tiddly-i.
Another fundamental purpose of functions is
to abstract code (by hiding details) and to modularize
the code, avoiding repetition and allowing an update in a definition to
affect any user of that definition. We saw this reasoning about
abstraction and modularity in a slightly different form in CSS style
sheets. It's the same idea.
So, let's introduce a function into the code above to take care of
that pesky bit of nonsense, which we'll name the refrain
for
lack of a better term. Here's the code (v2):
The function
keyword introduces a function definition.
We'll get to the precise syntax below, but for now, just know that the
code in braces is the definition of the function: it's the
code that gets executed when the function is invoked. We invoke the
function twice in the code below the function definition: that's
the refrain()
code.
Notice how defining and using this function solves the problem of
switching from hi-ho the derry-o
to Ee-i, tiddly-i
or
any other bit of nonsense and have it properly substituted in the
correct places in the song. It doesn't seem to have saved us much
typing, but you can imagine how much it might save if the definition
were very long and complex.
For example, a function that prints the day (with name of the day of the week) time (in 12-hour format) and other information might well have a very long definition but would also be useful in a wide variety of contexts.
Note the distinction between defining a function and invoking one. Unless it is invoked, the function will not execute. Why would you ever define a function unless you were going to invoke it? Typically, you wouldn't, but you might be building a library of useful functions for someone else (even yourself) to use. We'll see how to build a library of JS functions below. jQuery is just such a library, and there are many others.
The ability to write something once and use it over and over is called modularity, a central principle in software engineering. Building programs out of reusable, mix-and-match units makes code easier to write, because you can write and debug a function once and then use it over and over again. The code is also easier to understand, because you can read and understand the function once and then know what's happening whenever it's used.
Let's look more precisely at the syntax of a function definition:
<script> // comment about your lovely function function functionName() { . . JavaScript code to define the function goes here . . } /* You would invoke your function in other JavaScript code, in this other <script> elements or in event handlers. */ </script>
To define your own function you need:
It is also considered good practice to put a comment before 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.)
Now that we're a bit more comfortable with functions, let's use more of
them. The refrain()
function is invoked from several places,
but it can be nice just to use functions to structure
the code,
naming chunks and pieces of it, so that the top-level function can be
simpler and clearer. Here's an example (v3):
Here is where we define a bunch of functions:
Notice how simple, even trivial, some of the function definitions
are. This is not a bad thing! If something has gone wrong in the
second verse, we know exactly where to look for the error, and we can
easily ignore irrelevant code, such as refrain()
or verse1()
.
Finally, we can write the song into the page by invoking
the song
function, as follows.
So far in this course, you could count on things happening in a web page in a sensible order: top to bottom and left to right. If one tag comes before another in the text of the web page, it happens first. This sensible ordering didn't change when we got to JavaScript: JS also goes top to bottom and left to right. Then we got to conditionals, and things got a little odd, because we could now skip some lines of code. Still, we never went backwards or anything foolish like that. Alas, not any more.
As a synonym of the verb invoke,
computer scientists use the
verb call
, as in Alice called Bob on her cell phone
or this
With that terminology in mind:
script
element calls (invokes)
the alert
function.
The order of execution with functions is that the caller executes first, top to bottom, but when the caller gets to the function invocation, control transfers to the "callee" (the function), which then executes top to bottom, and, when the function gets to the end, control returns to the caller, which continues from where it left off.
This all sounds very abstract, so let's go back to the multi-function version of Farmer in the Dell as a concrete example.
Observations:
refrain()
is defined first, it
doesn't execute first.
refrain()
is called
from verse1()
or from verse2()
, it always
returns to its caller.
verse1()
happens to be defined
above/before verse2()
, but the code would
have exactly the same behavior if they were defined in the
other order. In fact, the order of all four function definitions is
completely unimportant. As long as all four are defined
before song()
is invoked, all is well.
As you can see, the body of a function follows the same top-to-bottom, left-to-right execution rules that we've always had. However, when the computer gets to a function invocation, it jumps to the beginning of the invoked function, executes that, and, when the function is done, returns to where it jumped from. Thus, it takes a round-trip journey to the function code. If the callee invokes another function, there is another round-trip within the outer round-trip. (These levels of round-trips can be embedded as deeply as we like.)
The thing to keep in mind, then, is that your function is not executed when the browser reads it, but only when some code invokes your function.
There is another pattern in the nursery rhyme that you already noticed, which is that the unique phrase of each verse is repeated three times. In fact, you might have thought about defining the verse functions like this (v4) :
Of course, we have to invoke the song function in order to write the song into the page:
The simplification of the two verse
functions reveals a
deeper pattern: the verse structure is identical, with the exception of
the value of that phrase
variable. Wouldn't it be nice to
have an abstract verse()
function that handled all the
verses, instead of a separate function for each verse, as if they had
nothing in common?
The following code (v5) does that:
Once again, we have to invoke the song
function to start
everything going:
Note how clean and compact the definition of verse()
is!
Nevertheless, there's some new stuff here, namely parameters.
A function can specify that it takes one or more inputs.
In the
function definition, these are a list of names enclosed in the
parentheses after the function name. These names are
called parameters.
Parameters are the names of variables that will be created every time
the function is invoked. In the example above, the
variable phrase
is created and given a value each time
the verse
function is invoked. In order to specify what
values these that parameter will hold when the function executes, the
function invocation must be modified to include
arguments — that is, comma-separated expressions
within the parentheses after the function name. In the example above, the
invocations are modified to include the text of the actual verse.
We have one more thing we will do with the Old MacDonald song, but first, let's take a digression to look at some other functions.
Most of you first learned about functions in math class, and historically, the first computer languages used them the same way. We won't do much math in this course, but for the next few examples, we'll do a little very simple math. You're all familiar with the general equation of a line, that goes something like:
y = mx + b
or, using a notation with functions:
f(x) = mx + b
for particular line, we use particular values for the slope and y-intercept, such as:
f(x) = 3x + 2
In JavaScript (as we are using it in this class), we would might define
a function to write the result using console.log
.
The "\n" at the end of the result string stands for the "newline" character, which goes to the next line in many circumstances. We use it here for a bit of simplicity. Don't worry about it.
Execute this box (or change the code) and switch to the JavaScript console (using Firebug, for example), to see the output.
Note that this mathematical function brings out an important way to think about functions and parameters:
x
. These names are the names of
the parameters.
Thus, functions with parameters are hard to understand for the same reason that algebra is harder than arithmetic: most middle-school students struggle with abstract mathematical/algebraic expressions like a(b+c) even though they have no trouble with specific arithmetic expressions like 2(3+4).
Let's return to our function that computes the y-value for points on a line and writes the result into the console.
Of course, switching to the JavaScript console is a little
inconvenient. What would we do if we wanted to write the value into our
document? We would, of course, create a destination element, such as the
following div whose ID is f1_output1
.
This is f2_output1.
And we would re-write our function (we'll give it a new
name, f2
) to append to that location, instead of using the
console:
Try some values:
This works pretty well. But it's a little inflexible. What would we do if we wanted to compute the function but insert the result someplace else in our document? We'd have to re-write the function. That's not difficult in this case, but what if the math is very complicated, written by someone else, and loaded from a library?
For example, maybe the function is part of some tax calculation and it's written by the IRS (the Internal Revenue Service) and defined in some external JavaScript library. If the function always writes the output in an element with a particular ID, it would be frustratingly inflexible. Perhaps we don't expect better from the IRS, but we will try to do better.
Let's re-write our line function so that it can specify the output
destination. That is, the function definition won't know where to write
the result any more than it knows the value of x
. Instead,
it will be told where to write the result, using a parameter,
just like it is told the specific value of x
to use for a
particular computation.
Here's the definition we will use. So that you can easily compare it with the inflexible definition, I've repeated the inflexible definition here, modified to write the output to a different element
Here are several places you could put the result:
This is f3_output1.
This is f3_output2.
This is f3_output3.
Let's try it out:
You can see that the f4
function is able to put the output
anywhere, while the f3
function can only put the output into
one place. In f3
, the destination where the answer goes is
hard-coded
. (Computer Scientists use hard-coding
to mean
that something is inflexible: the caller has no choice in the matter.)
Of course, that flexibility comes at a (small) price: the caller now
has the responsibility of specifying where the output is to go. If we
omitted that second argument, treating f4
like f3
, it would just fail: jQuery wouldn't know where to
write the result, and so it wouldn't go into our document.
Let's return to the Farmer in the Dell song, and allow it to be flexible about where the output goes. The following code (v6) is our ultimate version:
Now, when we want the song written into our page, we must specify what
element we want it written into, just as we did with f4
.
Notice how easy it now is to change where the song is inserted onto the page! Also, notice that each function uses a different parameter name to stand for the place to put the output into:
song()
names it output
verse()
names it where
refrain()
names it selector
It really doesn't matter what the function calls the parameter, any
more than it matters whether f4
names its first
parameter x
. (The code would work just as well if we renamed
all the x
variables to x_value
or horizontal_axis_position
or even nonsense names
like fred
). As with variables, you should name your
parameters something meaningful, but beyond that, you have great latitude
in what you name them.
Finally, notice how compact our code is becoming. It's almost shorter
than the song, and the structure of the code really reflects the way the
song is organized. Moreover, we can generate another four-line verse by
just adding one line of code to the definition of song
.
When a function is called, the values of the argument expressions are stored in newly created variables with the parameter names, and the statements in the body of the function are executed. The argument values are matched up with the parameter names in order from left to right. For example, here is a function to compute someone's BMI and insert it onto the page. (BMI is calculated as a person's weight in kilograms divided by the square of their height in meters.)
The following picture shows the correspondence of the particular arguments (150, 5, etc.) and the parameter names:
Obviously, it's important to get the order of the arguments correct: you don't want to accidentally compute the BMI for someone who is 7 foot 5 (instead of 5 foot 7) or even with a weight of 5 pounds and height of 7 foot 150!
Another important thing to understand about parameters is that the storage location corresponding to a parameter only lasts as long as the function is executing. Furthermore, the parameter is a copy of the argument value, so the function can't modify a variable that it is given. Here's an example:
(Check the error console to figure out what happened to that
last $("#number_output").append()
.
Note that the x
parameter of the next()
function is only meaningful within that function. Any
other x
in any other part of the code means a different
variable, if one even exists. For example, each of the following two
functions uses the parameter h
, yet these two parameters
have nothing to do with each other.
It happens that both of these functions have two parameters, the first
a number and the second a string. Inside the function, there are
several names, h
, where
and selector
. The first parameter of each function is
called h
, but they have different values (2.5 versus 200)
and different meaning. They happen to have the same name, but they are
entirely different variables. The second parameters have the same value
(the string "#output2"), but that's just another coincidence. They do
have the same meaning, namely where the result will be
appended, even though they have different names.
(You might object that it would be clearer to name these parameters
something more specific than h
, and you would be right, but
the issue of happening to use a name that is used elsewhere never goes
away.)
There are technical terms for these issues, and sometimes technical
terms can bring out the important aspects of the examples. The issue of
where a name has a single meaning is called scope
, so we
would say that the scope of the h
parameter is within the
function definition. That's why x
was undefined outside
the next()
function.
The limitation on the scope of a variable name is a good
thing, and it's related to this issue of modularity. The
implementers of the hardness
and is isTall
functions don't have to coordinate about or even discuss what they
name their parameters; the decision is entirely theirs.
The issue of how long a variable lasts is called extent
.
Parameters are very short-lived, they cease to exist when their function
is finished executing. Variables that aren't inside a function
definition are called global. Global variables last as long as
the page is loaded in memory. [There are other kinds of extents, but
this will do for now.] Global variables are called that because their
scope is everyplace (within that file).
Note that the dieSlideNumber
variable in
the dice slide
show example is a global variable.
One last example on the topic of global variables. The following code sets up a rectangular div whose background is a dark green, and on it is an image of a tennis ball. The dark green rectangle is the "court." The image of the tennis ball is positioned using absolute positioning, setting the top of the ball to be 0px down from the top of the court and the left edge of the ball is 100px from the left edge of the court.
Now for some JavaScript. We will define a global
variable, x_position
to keep track of the left position of
the ball. Two functions will update that variable, making the number
bigger or smaller. The updated position will then be used to change the
position of the ball image, using the jQuery css
method.
These two functions will then be attached as handlers for
the click
event handlers for the two buttons. (We'll learn
about buttons when we get to forms.)
Try it! Click the buttons to make the ball go back and forth!
Let's take some time to explore the functions that you are using in homework 5: promptDayHourMinute.js, or, with fancy syntax highlighting: promptDayHourMinute.js (highlighted)
Using this tennis demo page, let's define some variations on the functions above that moved the tennis ball.
restart
that puts the ball
back to the beginning (left
equal to 0). Then make a
button to invoke it.
winner
that moves the ball
all the way to the right (left
equal to 700px). Make a button to invoke it, too.
The functions left25()
and right25()
we
defined above for the tennis example were fine but they miss an
important pattern. Look at their code; it's nearly identical! Let's
improve that.
moveBy
that moves the ball
relative to its current location by a given amount. The amount is
given to the function as an parameter.
left 25pxand
right 25pxwith calls to the new
moveBy
function.
How could we make the functions keep the ball in bounds?
This is a hard exercise. By all means think about it yourself, but we will probably talk about this in class.
Let's now explore an example using
user-defined functions. Write an HTML document to make a similar
page. In your program, define a global variable called
amountToPay
which will store the user's total shopping
bill. Be careful to initialize it properly. Define a function
addToTotal()
that updates and displays (via an
alert()
) amountToPay
each time the user
makes a purchase. addToTotal()
should take a single
parameter: the price of the purchased item. Your page should have a
single form with multiple buttons (in our case, there are five).
For a simple version, each
button will have its own click
event handler that invokes
addToTotal()
, passing in the cost of the item purchased.
For a more sophisticated version, define just one event handler that
uses a data structure (a JavaScript object) to look up the price by the
ID and looks up the ID of this
.
After you've worked on it on your own, you can view the source to see the solution.
(This section is complex, but it's strongly related to the Farmer in the Dell series of examples. You should read it, but don't sweat over understanding it completely.)
JavaScript functions are a powerful way to abstract over patterns in JavaScript code as well as in HTML and CSS code.
As a practical example, suppose you want to create a page of links to CS110 student web pages. Such a page has a list of items that could be specified by HTML code like the following:
But such a list would be very tedious to create by hand for a class
with lots of students. It would also be difficult to modify, e.g.,
suppose we want to add a hw2
link for each student. How much
work would that be?
An alternative approach is based on the observation that all the list
items are exactly the same
except for (1) the last name, (2) the first name, (3) the server
account name, (4) the year, (5) the lecture section, and (6) the
discussion (lab) section. So we can create a function (let's call
it writeStudent()
) that takes these six elements as
parameters (assumed to be string values) and uses
jQuery's append
method to construct one of the list items.
We can then call the writeStudent()
function for each student
with the appropriate six string values for the parameters, as shown below:
function writeStudent(where, lastName, firstName, account, lecture) { $(where).append("<li>" + "<strong>" + lastName + ":</strong>" + lastName + "," + firstName + ", " + account + ", " + "lecture " + lecture + ", "); $(where).append("[<a href='http://cs.wellesley.edu/~" + account + "'>public</a>], "); $(where).append("[<a href='http://cs.wellesley.edu/~" + account + "/protected'>protected</a>], "); $(where).append("[<a href='http://cs.wellesley.edu/~" + account + "/hw1/hw1.html'>hw1</a>]"); } writeStudent("#stu_list","Abbott", "Barbara", "babbott", "L03"); writeStudent("#stu_list","Babbage", "Ada", "ababbage", "L02"); ... omitting many list items ... writeStudent("#stu_list","Zuse", "Elizabeth", "ezuse", "L01");
Now to add an hw2
page for every student, we can simply
insert add additional call to $(where).append()
, copy/pasting
and editing the code for doing hw1. This is way easier than
adding an extra link for every student!
There is a small technical flaw in the code above. It
will work, but the HTML isn't quite valid. Furthermore, writing
complex HTML in a string like that is hard to get right; one missing
quotation mark can mess it up entirely. In a later lecture, we'll
learn an even better technique, using jQuery's cloning, which is much
easier to debug than the complicated argument to
the append
method.
But we're not yet done with capturing patterns. Note that most of the code for the hyperlinks is almost exactly the same. This suggests that we should abstract over this pattern by writing a new function:
function appendURL(where, userName, file, text) { $(where).append(" [<a href='http://cs.wellesley.edu/~" + userName + "/" + file + "'>" + text + "</a>]"); } function writeStudent(where, lastName, firstName, account, lecture) { $(where).append("<li>" + "<strong>" + lastName + ":</strong>" + lastName + "," + firstName + ", " + account + ", " + "lecture " + lecture + ", "); appendURL(where, account, "", "public"); appendURL(where, account, "protected", "protected"); appendURL(where, account, "hw1/hw1.html", "hw1"); appendURL(where, account, "protected/hw2/hw2.html", "hw2"); } // these function calls are unchanged! writeStudent("#stu_list","Abbott", "Barbara", "babbott", "L03"); writeStudent("#stu_list","Babbage", "Ada", "ababbage", "L02"); ... omitting many list items ... writeStudent("#stu_list","Zuse", "Elizabeth", "ezuse", "L01");
Notice that even though we changed the code of
the writeStudent
function, we didn't have to change any of
the invocations. That's because it did the same thing, with the same
arguments, but did it in a different (better) way. This ability to
improve the implementation of something without having to re-write all
the callers is yet another aspect of modularity and good program
design.
In our examples so far, we have defined JavaScript functions
in script
elements in our document. But just as it's
possible to put CSS stylesheets in an external file that can be imported
by many HTML documents, it's possible (and usually preferable) to define
JavaScript functions in an external file that can be imported by many
HTML documents.
In fact, earlier we mentioned the idea of creating a library
of
useful functions. Writing a bunch of function definitions into a separate
JavaScript file and allowing others to load it is a simple yet sufficient
step to create a library.
For instance, in our student list example, we could put the two
functions in a file named students.js
:
// Contents of the file students.js function appendURL(where, userName, file, text) { $(where).append(" [<a href='http://cs.wellesley.edu/~" + userName + "/" + file + "'>" + text + "</a>]"); } function writeStudent(where, lastName, firstName, account, lecture) { $(where).append("<li>" + "<strong>" + lastName + ":</strong>" + lastName + "," + firstName + ", " + account + ", " + "lecture " + lecture + ", "); appendURL(where, account, "", "public"); appendURL(where, account, "protected", "protected"); appendURL(where, account, "hw1/hw1.html", "hw1"); appendURL(where, account, "protected/hw2/hw2.html", "hw2"); }
Then we can use the functions in students.js
in any HTML
file by using the
src
attribute of the script
tag:
<!--import jQuery into this HTML file.--> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script> <!--import the contents of students.js into this HTML file.--> <script src="students.js"></script> <ul id="class_roll"> </ul> <script> writeStudent("#class_roll","Abbott", "Barbara", "babbott", "2011", "L03", "D05"); writeStudent("#class_roll","Babbage", "Ada", "ababbage", "2013", "L02", "D01"); ... omitting many list items ... writeStudent("#class_roll","Zuse", "Elizabeth", "ezuse", "2010", "L01", "D04"); </script>
Things to note about functions:
refrain();
verse("Farmer takes a wife.");
<SCRIPT>
tags.
Optional reading: Thau Chapter 6.