Writing Functions - Advanced JavaScript - JavaScript, 20 Lessons to Successful Web Development (2015)

JavaScript, 20 Lessons to Successful Web Development (2015)

PART II Advanced JavaScript

LESSON 12 Writing Functions

image

To view the accompanying video for this lesson, please visit mhprofessional.com/nixonjavascript/.

Apart from using conditional statements such as if() and switch(), and loops such as while() and for(), there’s another way you can control program flow called the function. Functions are sections of code that you call from any other part of code (or even the function itself, which is known as recursion), and which then perform one or more actions and then return.

When functions return, they may also return a value back to the calling code, or they can simply return without doing so, in which case the returned value will be undefined. Interestingly, as you will learn in Lesson 13, in JavaScript, functions are also objects so they can be passed as values, used in arrays, and so on.

Using Functions

JavaScript comes with many inbuilt functions. For example, to obtain the square root of the number 49, you can call the Math.sqrt() function, like this, which will return the value 7:

document.write(Math.sqrt(49))

The optional value you pass to a function is called an argument, and you can have any number of these arguments, or none. In the case of Math.sqrt(), a single value is required. The square root of that number is then calculated, and the value derived is returned. That’s how thedocument.write() call in the preceding example can display the square root value, because that value is returned directly to the calling code, which is the document.write() call.

There are two types of functions: named and anonymous. You can create named functions using the keyword function followed by the name to give to the function, and then a pair of parentheses, within which you list the arguments being passed to the function, separated with commas. The code of the function must be enclosed within curly braces.

Following is what the code to emulate the built-in Math.sqrt() function might look like, based on the fact that the square root of a number can be calculated by raising that number to the power of 0.5—with Math.pow() serving to calculate the power:

image

In this example, the function created is SquareRoot(), and it accepts one argument (the value passed in the variable n).

The function code comprises a single statement that simply calls the inbuilt Math.pow() function, which accepts two values: a number and a value by which power the number should be raised. So the two values passed to it are n and 0.5.

The return Keyword

The function then calculates the square root and returns it, at which point the return keyword causes that value to be returned. It is then a simple matter of calling the function in the following manner to display a square root in the browser:

document.write(SquareRoot(49))

Or, the value returned can be used in an expression, assigned to a variable, or used in numerous other ways.

image

Of course, this code slightly cheats because it calls another inbuilt function called Math.pow() (in which case we might as well simply call the inbuilt Math.sqrt() function in the first place), but it serves to illustrate how to write a simple function that takes one value and returns another after processing it.

Passing Arguments

In the preceding example you saw how to pass a single argument to a function, but you can pass as many as you need (or none), as shown with the following function that provides functionality that is not native to JavaScript (but is in some other languages), namely the ability to create a string by repeating a supplied string a set number of times.

For example, the PHP language provides a function called str_repeat(), and the following code gives this same functionality to JavaScript:

image

This function uses the sneaky trick of creating a new array with the number of elements in the value r, plus 1. So if r has the value 3, then the new array is given four elements by pre-incrementing the value in r, resulting in a statement equivalent to new Array(4), as described in Lesson 5.

With the array now created, the join() function is called by attaching it to the Array() function using a period. As you recall from Lesson 7, join() concatenates all the elements in an array into a string, placing the separator string in the value passed to join() between each element value.

Therefore, if r has the value 3, a four-element array is created (with each element being empty). Then the join() function concatenates these four elements together, placing the string in the variable s between each occurrence. Therefore, because the array elements are empty, this entire statement will simply create three copies of the string in s concatenated together, and that is the string that is returned from the function using the return keyword. Neat, huh?

Accessing Arguments

Arguments received by a function are given the names you supply between the parentheses. These do not need to be (and probably will mostly not be) the same names as the variables or values passed to the function.

Variables are assigned to the values received by a function in the order in which they are listed, and there can be as many or as few arguments as you like. Generally, the number of arguments supplied to a function should be the same as the number the function expects to receive, but not always.

If a function receives fewer arguments than it is expecting, it will assign the value undefined to the remaining values, as shown in the following example (see Figure 12-1) in which the third argument has not been passed:

image

image

FIGURE 12-1 The third argument has not been passed to the function.

If your function sometimes uses the missing values and sometimes doesn’t, this can cause an obscure and hard-to-track down bug. But there are times when you may not want to provide all the arguments to a function, because they may be optional.

For example, consider the inbuilt JavaScript function join() that joins the elements of an array together into a string. It accepts either no argument or an argument that will be used as the divider between each array element. If no argument is supplied, join() assumes a separator string of′,′.

You can write code to perform the same function as join() like this, which creates a function of the same name, but with the first letter of the name capitalized (for simplicity, I have omitted the actual code that does the joining):

image

The key code that provides a default value works like this:

if (separator == undefined) separator = ′,′

This can also be achieved using the ternary operator (as described in Lesson 4), thus avoiding the use of an if() statement, like this:

separator = !separator ? ′,′ : separator

In this case, the use of the ! symbol before the separator variable stands in for a test for separator having a value of undefined, because the expression returns true if separator is undefined and false if it is defined.

You can also rework this expression to not require the ! symbol and still achieve the same result, like this (which reverses the order of the second and third arguments):

separator = separator ? separator : ′,′

image

If separator is passed as the value 0 or an empty string, the preceding code will not work as intended. Therefore, if you plan to support these two values, you should stick with simply testing whether the separator is defined or not. Incidentally, another way to test a variable (or other object) for not being defined is to use the typeof keyword, for example, if (typeof separator == ′undefined′). With typeof you can also determine whether an object is an array, a string, and so on.

Using the arguments Object

Rather than passing and accepting a known number of arguments, you can also access an object that is passed to every function called arguments, which contains every argument passed to the function.

You can access the elements of arguments as if it were an array using an index (from 0 to the number of elements in the object minus 1), or iterate through it like this:

image

image

Though the arguments object is not an actual array, it is array-like, and even though you can find its length with arguments.length and can access individual elements using an index, you cannot use any array functions on it such as join(), because they are set to work only with objects that are of the type Array.

Let’s look at one example where you might find accessing this object useful by creating a function that accepts any number of arguments and then joins them together into a string, which is returned:

image

This is a particularly useful function to have on hand when you wish, for example, to display an e-mail address in a browser, but make it extremely difficult for e-mail harvesting programs to discover, by breaking the e-mail address (or any other section of text) that you wish to obfuscate, like this:

image

This code will make little sense to even the most sophisticated e-mail address harvester, as there are none that I know of that will actually interpret JavaScript to look for e-mail addresses. The result of executing this statement displays in a browser as follows:

jamesjones@jjinc.com

Because document.write() can, in some circumstances, overwrite the current document, it is not always the best solution for outputting text. Sometimes, it is better to supply such content to the innerHTML property of an object, as this is much safer. For example, you can replace the preceding code with this (arguments4.htm in the accompanying archive), which also places the e-mail address in a clickable link:

image

image

Using the this Keyword

I didn’t show you all the code to replicate the join() function in the previous section, so let’s do that now, using the this keyword. Because join() can operate on an array without you passing it any argument at all, you may wonder how it knows which array it must work on. The answer is that the array is passed to join() using the period operator, like this:

Array.join()

When the code for the join() function begins execution, it obtains the array in the this keyword, so we can finish off the new Join() function as follows:

image

Before the function definition, there is a statement that adds the ability to use the new Join() function on Array objects. It uses the prototype keyword, which is explained in much more detail in Lesson 13 (basically, it allows you to add properties and methods to an object). For now, all you need to know is that the statement allows the function to work.

Inside the function a variable called string is initialized to the empty string (′′). Its value will later be used to return the string created by this function. After that, if separator is not defined, it is given the default value of ′,′:

image

Then a for() loop is used to iterate through all elements in the array this, except for the last one. You will recall that the this keyword contains the argument supplied via the period operator—in this case, an Array object:

for (j = 0 ; j < this.length -1 ; ++j)

Each time around the loop, the value in the current array element is appended to the variable string, followed by the separator:

string += this[j] + separator

Finally, once the loop has completed, there is one element remaining in the array that hasn’t yet been accessed, and so that is appended to the end of string, and then the value in string is returned:

return string + this[j]

Code such as the following can now be used to access the new function:

image

This code creates a three-element array with the names of three types of pets, then it passes that array to the Join() function using the period operator and also supplies the string ′ and ′ to be used as a separator. Figure 12-2 shows the result of loading the code (this.htm in the companion archive) into a browser.

image

FIGURE 12-2 Using the this keyword

image

As well as using this to pass values using the period operator, in Lesson 13 you’ll learn how the this keyword is also very useful for attaching functions to JavaScript events.

Anonymous Functions

In JavaScript it is not always necessary to give a name to a function, and functions without names are called anonymous functions. One reason for using an anonymous function is when it is called only once by one statement, and so for reasons of logic and code readability, the function is inserted anonymously in the code at the point where it is needed.

For example, as you will learn in Lesson 16, it is easy to attach JavaScript functions to events that occur in the browser. For example, you may want to execute a couple of actions when a mouse passes over an object, and a function is a good way to do this, as follows:

image

But if this function is only to be called at this particular point in the code, you can simplify things by making the function anonymous, like this:

image

Now you are no longer cluttering up the JavaScript name space with the function name DoThese, and the code is shorter and sweeter.

image

If you want to reuse the function code in other places, it then becomes wasteful to use it in anonymous functions, because you will end up with several occurrences of the function’s code. Therefore, anonymous functions are optimal only when they will be called by a single statement.

Global and Local Variable Scope

Up to this point I have left out a very important keyword that you will certainly have seen if you have viewed the source of any JavaScript code, and that’s the var keyword. After introducing it here, you’ll see me using it a lot more. However, I left out its inclusion until now because I didn’t want to get you bogged down by the difference between local and global variables. But you are ready for it now!

So far I have treated all the variables created in the book as having global scope. This means that once defined, you can access their values and modify them from any other part of a program. But often this isn’t desirable because you can start to run out of good variable names and, as a program gets longer, so will your variable names.

More than that, the larger and more complicated a program gets and the more people working on it, the greater the chance that you may inadvertently reassign a value to a variable that is being used in another part of the program, resulting in name clashes and weird values creeping in, creating very difficult-to-trace bugs.

Using Local Variables

The solution to a sprawling name space packed with numerous variable names is to allow functions to reuse a variable name without it affecting the value of any variable with the same name used outside of the function. And the way this is achieved is with the var keyword.

To tell JavaScript that a variable you are using within a function should have local scope only, you simply precede it with the var keyword where it is first assigned a value, like this:

var MyVar = 42

From then on, this variable will have its value only within the function call (and any subfunctions that might be created within it). When the function returns, the variable’s value is forgotten, and if there is a global variable of the same name, it will retain its value because local variables don’t affect it.

image

There is no point whatsoever in using the var keyword outside of a function, as you often see in examples of code on the Internet, because the program scope at that point is global and so applying local scope in a global environment results only in the variable having global scope. One reason instructors may do this, though, is when they think you may take their sample code and place it within a function. In this case it really is a good idea to make all variables that are used purely within the function have only local scope. Therefore, it can be a good habit for beginners to adopt.

To illustrate how to use the var keyword, let’s revisit the Join() function we looked at a little earlier, but this time with var keywords in the right places (shown in bold text):

image

As you’ll recall, this is the replacement function written to emulate the built-in join() function. But as previously written, it was wasteful on global name space by treating both string and j as global variables, when there was no valid reason for this, and it could cause a bug if there were any existing global variables of these names.

Instead, because string and j are meant for use only in passing, the first time each is accessed (even if it’s inside the setup part of a for() loop, as in this case), it is preceded by a var keyword, which ensures it will only have scope within this function.

image

Once the var keyword has been applied to a variable, it does not need to be done again. The local scope will remain until the function returns.

In the following example, the variable Fred is assigned the value 1. Because the assignment occurs outside of any functions, it has global scope, which means its value can be accessed from any part of the program.

However, within the function MyFunc() the variable Fred is reused, but with the var keyword preceding it, so it has local scope only.

image

Following the program flow, at the first document.write() call Fred has a value of 1. Then MyFunc() is called and, within the function, the local variable Fred is assigned the value 2. After displaying its value, the function returns (no return keyword is used because there is no value to return from this function).

Upon return, the value in Fred is again displayed, and it is back to 1 again, because when Fred is referred to outside of the function, it refers to the global variable, whose value remains unchanged at 1.

Figure 12-3 (created with var.htm in the companion archive) shows the result of running this code in a browser.

image

FIGURE 12-3 Using local and global variables of the same name

image

What is happening here is that two separate variables have been used. They may have the same name, but because the one in the function is given local scope, it is quite different from the one outside the function. I would also like to mention in passing that the keyword var is not actually very helpful in that it doesn’t really describe what it does. In my view the keyword local would have been a much better choice. Nevertheless, var is the word that has been chosen—just remember that it always applies local scope (and only works within a function).

Global Naming Convention

I write a lot of JavaScript code and found that for each variable used I would still have to keep referring back to see whether it had a var keyword applied at any point in a function (making it local), or if no var keyword was used, it would then be global. To save me from having to keep rechecking, I came up with the following simple convention.

Whenever a variable is created that should have global scope, I use all uppercase letters, like this:

image

Therefore, I can be sure that any variables I use that have any lowercase letters are being used as local variables, and I also ensure I use the var keyword on their first use in a function.

Of course, you can use any other convention you like (such as prefacing global variables with G_), or no convention at all.

image

You might ask whether separating global and local variables by uppercase and lowercase obviates the need for the var keyword, as local variables will never compete with global ones. However, that’s not the case because all variables would then be global and we would be back at having to choose lots of different non-uppercase variable names for use in functions in order to avoid them conflicting with each other. Therefore, using uppercase for global variables simply makes it clear at a glance which ones are and aren’t global, so you never have to go hunting for var keywords to understand the various scopes of variables in a complex function.

Summary

Congratulations! With the use of functions under your belt, you can now call yourself a JavaScript programmer. However, there are still a few more steps to take before you can call yourself a master of the language—starting in the following lesson with JavaScript objects, which enable you to write OOP (Object-Oriented Programming).

Self-Test Questions

Using these questions, test how much you have learned in this lesson. If you don’t know an answer, go back and reread the relevant section until your knowledge is complete. You can find the answers in Appendix A.

1. What is the main purpose of a function?

2. What is an anonymous function?

3. When should you not use an anonymous function?

4. What is the main means by which a value is returned by a function?

5. How can values be passed to a function?

6. How does a function access the values passed to it?

7. What is another way of accessing the arguments passed to a function?

8. In what circumstance is it preferable to use the arguments object rather than named arguments?

9. How is the this object used by a function?

10. How can you tell a function that a variable is to be used only locally?