In the previous chapter, we learned about the nuts and bolts of how programming works, with assignment statements, conditional branching, and loops. For small programs, this is all you need. In fact, you can do any possible computation you might need to do with only these features. However, as your programs get larger—even just a little bit larger—you will need tools that will enable you to get more work done with each line of code and to organize your code into logical blocks. Imagine, for instance, if your program was 10,000 lines long. If you needed to change a line, it might be hard to find!
Likewise, let’s say there was a task that you had to do over and over again. If you had to type out the code for it each time, that would be a lot of wasted effort! In addition, let’s say that you found an error (known as a bug) in your code that you had been copying. If you had 20 copies of the code, you would have to find each copy and fix it. This is tedious, wasteful, and error-prone.
10.1 Your First Function
You already have some experience with functions. Remember parseInt, prompt, and alert? These are known as built-in functions because they are part of JavaScript itself. However, you can define your own functions that you can call in the same way.
To begin our discussion of functions, let me show you a simple program that illustrates how functions work. Remember to wrap this and the other programs in this chapter in an HTML file like we described in Section 8.3. Figure 10-1 shows the code. What this program does is define a function which takes a single value and returns that value squared (i.e., multiplied by itself). It then uses that function twice, giving it different numbers to square.
The function in this code is defined using the function keyword. After the function keyword comes a parameter list—a list in parentheses of all of the parameters that the function can take. The function we are defining takes a single parameter, which we have named num. This means that the value that is sent to the function gets stored into num for the duration of the function, so we have a name to call it by. Since it is in the parameter list, we do not use the var keyword to define it.
In short, function(num) tells JavaScript to define a function that takes a single parameter and name that parameter num. When we use num within the function, we will be working with whatever value was sent when the program was called. We could have used any valid name instead of num, but num seemed like an appropriate name for a number.
Inside the curly braces is the block of code that defines what the function will do. In this case, we are creating a new variable called value, multiplying num by itself, and then storing it into value. Then, the return statement tells JavaScript what to give back to the code that called our function. In this program, the return statement says that the result currently in value should be given as the result for the function.
The function operator defines a function. However, we need to be able to call the function from our code. It therefore needs a name. How do we name something in JavaScript? We store it in a variable. As you can see in the code, we are storing the function in a variable we created named square_a_number. As usual, don’t forget the semicolon (;) at the end of the assignment statement, or JavaScript might get confused.
Now that we have the variable square_a_number which contains our function, we can call it exactly like we called other functions like parseInt. square_a_number(5) will yield 25, and square_a_number(6) will yield 36.
- 1.
In the same file as the square_a_number function, define another function called cube_a_number that returns the cube of a number (i.e., the number times itself and times itself again).
- 2.
Call the alert function a few times giving it the results of the cube_a_number function, like we did for square_a_number.
- 3.
The body of the square_a_number function creates a variable called value to store the temporary result of the calculation. However, because the calculation is so simple, we don’t really need this variable. Can you rewrite square_a_number so that it doesn’t use a variable?
- 4.
Change the program so that it asks the user to type in a number, then call square_a_number to calculate the square, and then show the result to the user.
- 5.
Take the program you’ve just written and put the interactions with the user in a for loop that runs three times so that it will ask the user for a number and give the result three times.
- 6.
Change the loop from a for loop to a while loop, and perform the operation until the user enters a zero for the number to square.
10.2 More Function Examples
To make sure that you grasp the concept of a function, let’s do another example. Let’s say that we wanted to have a function that summed up a range of numbers, like all of the numbers between 2 and 12. This would be similar to the code we wrote in Section 9.3.3, but it would take the start and end values as parameters. How would we write that? Well, we would need a function, but that function would also need to have a loop inside of it to loop through all of the numbers from the start to the finish. The code would look like Figure 10-2.
This function has a few differences from our previous function. First of all, it has two parameters instead of one—range_start and range_end. Functions can have as many parameters as you want. It can even have zero, which would be indicated by writing function(). In this case, we need two values because we need both a start and an end value for the range. In JavaScript, parameters are positional, which means that the order that you define them with the function keyword is the same order as the values that you have to give when the function is called. In other words, since range_start is the first parameter, the first value in the function call gets placed here. Since range_end is the second parameter, the second value that is sent to the function call gets placed here. So, calling sum_range(start_val, end_val) tells JavaScript to run the function sum_range, put the value of start_val into range_start, and put the value of end_val into range_end. It then runs the function and returns the value, which, in this case, is placed in the variable result. Next, we have a for loop embedded in the function. Just like any block of code in JavaScript, we can embed any type of statement or operator within a function.
For this small program, since we only use the sum_range program once, it doesn’t save us a lot of typing. But can you see how, if we needed it in several different parts of the program and used it over and over again, putting that code into a function will save a lot of typing and headaches in the future?
Even though this example doesn’t save us any typing, using functions still has a distinct advantage—it separates out different components of our application. In this program, we have both user interaction (prompting, receiving input, and displaying output) and computation (summing all of the numbers in a range). By putting the computation in a function, we have made both parts clearer.
Imagine if we had simply stuck all of the code together without a function. It might be difficult to even understand what the program was trying to do. Instead, by separating out a piece into a function with an easy-to-understand name (i.e., sum_range), then we make it clearer what the whole program is doing. You can look at it and say, “Oh yes, this piece of code with the name sum_rage sums up numbers within a range. And look! Over here we use the function with the two inputs from the user.”
Again, this is not as big of an issue with small programs, but when you write programs with many thousands of lines of code, having the code broken up into manageable, understandable components, each with a small, well-defined task makes the code much easier to understand and modify.
In general, separating out the user interface from the computation is a good idea in computer programming. This leads to a number of long-term benefits for the program itself. Many times in computer programming, user interfaces will change regularly even when the underlying logic stays the same. Therefore, having the core logic separated from its interface makes it easier for the program to grow and change without causing undue headaches. Oftentimes programs will have more than one user interface into the logic or perhaps a user interface and an interface that is used by outside programs. Here, separating out the core logic makes it so that this logic will not have to be repeated in each interface. Additionally, it is a good practice to write tests to make sure your code works. Testing core logic is usually more straightforward than testing user interfaces, so writing the core logic separately allows it to be more easily tested.
- 1.
Create a function that takes three parameters and returns the largest of the three parameters. You will have to use several if statements to accomplish this.
- 2.
Create a function called multiply which will take two parameters and perform the same function as the * (multiplication) operator. However, don’t use the * operator in your code. Instead, perform the task by repeated adding of the first parameter. Be sure to include code to run the function and display the result so that you know whether you did it correctly!
10.3 Functions Calling Functions
Functions can also call other functions. For instance, we can create a new function called sum_squares_for_range that is similar to the sum_range function, but, instead of summing up the numbers in the range, it calls square_a_number on each number to sum up the squares of the numbers in the range. Before looking at the code (Figure 10-3), think about how you might implement such a function.
As you can see, just as we can call a function from anywhere else in our program, we can call a function from within a function as well. Using this feature, we can make functions that are more and more complex. We can take several small functions that we often use together and write a larger function that combines them in an interesting and useful way. Likewise, if we have a large function that is difficult to understand, we can try to break the function up into well-defined pieces and create individual functions for those pieces.
10.4 Variable Scopes
If all variables had global scope, the only way to avoid this situation is to make sure that each variable had a unique name. This would be tedious and time-consuming, both to keep track of the variable names and to write the inevitably excessively long names that would result. Programming languages quickly adopted new scoping policies that allowed programmers more freedom.
To illustrate this, Figure 10-4 shows a program in which we modify a global variable inside the function. In this code, when it starts, it creates myvar as a global variable (it is defined outside of any function) and sets it to 3. Then, it calls my_function(). This function sets myvar to 5 and then returns. Now, when we show the variable again, it has the value 5.
Contrast that to what happens in the program in Figure 10-5. In this example, because myvar has the var keyword in front of it in the function, it creates a local variable that is specific to that function. It exists nowhere else. Even though there is a myvar that exists in the global scope, during the function myvar will refer to the local variable with the same name. Therefore, setting the local variable myvar from inside the function has no effect on the global scope. Both alerts will give the same value because the modification happened to a local variable.
Additionally, if there was another function that also had a locally scoped myvar, it would be a different variable than either the globally scoped myvar or the myvar that is locally scoped to this function. Each function’s local variables belong to that function and cannot even be referenced outside of that function.
Having local scopes allows functions to work as “black boxes,” meaning that the person who writes the code that calls a function doesn’t have to care about the details of how that function is implemented. If we only had the global scope, then, before I called a function, I would need to go and look up all of the variables it was using to make sure I wasn’t also using the same variable. However, if a function writer only uses local variables, then if I used that function in my program, I wouldn’t have to worry that it might accidentally overwrite a variable I am using. This is true in larger programs even if there is only one programmer. You will not remember the names of every variable you use in your functions. But, if you make sure that you only use local variables and parameters within your functions, you won’t need to remember all of the variable names since they will all be within the scope of the given function.
Occasionally, you will need to use the global scope. In fact, this is what we are doing on the names of the functions themselves—we are storing them in global variables. In JavaScript, functions are stored in variables just like any other value. Therefore, in order for functions to call each other, they must exist in the global scope. You can also define a function inside of another function, but we will save that discussion for Chapter 14.
10.4.1 Review
The function operator creates a function.
A function takes parameters, which are variables that refer to values passed to the function.
Function parameters are positional, which means that the order that they are defined using the function operator is the same order that the function call must use.
A function has a function body, which is the code that tells the computer what to do when the function is called.
Functions are stored in named variables so that they can be easily called. Programmers usually store functions in global variables so that the function is accessible from anywhere in the program.
Functions can call other functions.
Functions can be used to organize your code into well-defined, understandable units.
Functions can be used to minimize the amount of code that needs to be written by moving repeated sections of code into a function.
When repeated code sections are moved into a function, this also makes it easier to fix bugs as they only need to be fixed in one place.
A variable’s scope refers to the period when a variable becomes active and the places from which it can be accessed.
Variables in the global scope can be accessed from anywhere in your code.
Variables in a function’s local scope can only be accessed from within that function.
If a function has a local variable with the same name as a global variable, the global variable is hidden from view during that function.
If two functions have a local variable with the same name, these refer to two different variables because they each exist in different scopes.
10.4.2 Apply What You Have Learned
- 1.
Create a Celsius-to-Fahrenheit converter. It should ask the user for a Celsius temperature and return a Fahrenheit temperature. The conversion from Celsius to Fahrenheit is to multiply the Celsius temperature by 9, divide the result by 5, and add 32. Be sure that the actual conversion is contained within a function.
- 2.
Create a fuel efficiency calculator. It should ask for the number of miles you drove, how many gallons you used, and the price per gallon of fuel. It should then tell you the number of dollars per mile that it cost you to drive. The formula for this is to take the price per gallon, multiply by the gallons, and divide by the number of miles. Be sure that the formula is handled in a function.
- 3.
Implement the factorial function. The factorial function takes a number and multiplies together every number from 1 to the given number. For instance, the factorial of 6 is 6 * 5 * 4 * 3 * 2 * 1. The factorial of 3 is 3 * 2 * 1. Note that since you are repeating an operation, you will need a loop. Be sure that your factorial calculation is written in a function. Write a user interface that allows the user to enter the number they want the factorial of.
- 4.
Take any one of these calculators and make it so that the user can enter as many values as they want. You can do this by either asking the user afterward if they want to keep going or have a special value that the user types to signal that they are done. In any case, make it so that the user can keep using the application until they are ready to be finished.