JavaScript functions can be used to create script fragments that can be used over and over again. When written properly, functions are abstract—they can be used in many situations and are ideally completely self-contained, with data passing in and out through well-defined interfaces. JavaScript allows for the creation of such functions, but many developers avoid writing code in such a modular fashion and rely instead on global variables and side-effects to accomplish their tasks. This is a shame, because JavaScript supports all the features necessary to write modular code using functions and even supports some advanced features, such as variable parameter lists. This chapter presents the basics of functions, and the next two chapters discuss how, underneath it all, the real power of JavaScript comes from objects!
The most common way to define a function in JavaScript is by using the function keyword, followed by a unique function name, a list of parameters (that might be empty), and a statement block surrounded by curly braces. The basic syntax is shown here:
function functionname(parameter-list) { statements }
A simple function that takes no parameters called sayHello is defined here:
function sayHello() { alert("Hello there"); }
To invoke the function somewhere later in the script, you would use the statement
sayHello();
Note |
Forward references to functions are generally not allowed; in other words, you should always define a function before calling it. However, in the same <<script>> tag within which a function is defined you will be able to forward-reference a function. This is a very poor practice and should be avoided. |
Very often we will want to pass information to functions that will change the operation the function performs or to be used in a calculation. Data passed to functions, whether in literals or variables, are termed parameters, or occasionally arguments. Consider the following modification of the sayHello function to take a single parameter called someName:
function sayHello(someName) { if (someName != "") alert("Hello there "+someName); else alert("Don’t be shy"); }
In this case, the function receives a value that determines which output string to display. Calling the function with
sayHello("George");
results in the alert being displayed:
Calling the function either as
sayHello("");
or simply without a parameter,
sayHello();
will result in the other dialog being displayed:
When you invoke a function that expects arguments without passing any in, JavaScript fills in any arguments that have not been passed with undefined values. This behavior is both useful and extremely dangerous at the same time. While some people might like the ability to avoid typing in all parameters if they aren’t using them, the function itself might have to be written carefully to avoid doing something inappropriate with an undefined value. In short, it is always good programming practice to carefully check parameters passed in.
Functions do not have to receive only literal values; they can also be passed variables or any combination of variables and literals. Consider the function here named addThree that takes three values and displays their result in an alert dialog.
function addThree(arg1, arg2, arg3) { alert(arg1+arg2+arg3); } var x = 5, y = 7; addThree(x, y, 11);
Be careful with parameter passing because JavaScript is weakly typed. Therefore, you might not get the results you expect. For example, consider what would happen if you called addThree.
addThree(5, 11, "Watch out!");
You would see that type conversion would result in a string being displayed.
Using the typeof operator, we might be able to improve the function to report errors.
function addThree(arg1, arg2, arg3) { if ( (typeof arg1 != "number") || (typeof arg2 != "number") || (typeof arg3 != "number") ) alert("Error: Numbers only. "); else alert(arg1+arg2+arg3); }
We’ll see a number of other ways to make a more bullet-proof function later in the chapter. For now, let’s concentrate on returning data from a function.
We might want to extend our example function to save the result of the addition; this is easily performed using a return statement. The inclusion of a return statement indicates that a function should exit and potentially return a value as well. If the function returns a value, the value returned is the value the function invocation takes on in the expression. Here the function addThree has been modified to return a value:
function addThree(arg1, arg2, arg3) { return (arg1+arg2+arg3); } var x = 5, y = 7, result; result = addThree(x,y,11); alert(result);
Functions also can include multiple return statements, as shown here:
function myMax(arg1, arg2) { if (arg1 >>= arg2) return arg1; else return arg2; }
Functions always return some form of result, regardless of whether or not a return statement is included. By default, unless an explicit value is returned, a value of undefined will be returned. While the return statement should be the primary way that data is returned from a function, parameters can be used as well in some situations.
Note |
Sometimes these implicit return statements cause problems, particularly when associated with HTML event handlers like onclick. Recall from Chapter 4 that the void operator can be used to avoid such problems. For example: <<a href="http://www.pint.com"onclick="javascript: void x()">>Press the link<</a>>. Using void in this manner destroys the returned value, preventing the return value of x() from affecting the behavior of the link. |
Primitive data types are passed by value in JavaScript. This means that a copy is made of a variable when it is passed to a function, so any manipulation of a parameter holding primitive data in the body of the function leaves the value of the original variable untouched. This is best illustrated by an example:
function fiddle(arg1) { arg1 = 10; document.write("In function fiddle arg1 = "+arg1+"<<br />>"); } var x = 5; document.write("Before function call x = "+x+"<<br />>"); fiddle(x); document.write("After function call x ="+x+"<<br />>");
The result of the example is shown here:
Notice that the function fiddle does not modify the value of the variable x because it only receives a copy of x.
Unlike primitive data types, composite types such as arrays and objects are passed by reference rather than value. For this reason, non-primitive types are often called “reference types.” When a function is passed a reference type, script in the function’s body modifying the parameter will modify the original value in the calling context as well. Instead of a copy of the original data, the function receives a reference to the original data. Consider the following modification of the previous fiddle function.
function fiddle(arg1) { arg1[0] = "changed"; document.write("In function fiddle arg1 = "+arg1+"<<br />>"); } var x = ["first", "second", "third"]; document.write("Before function call x = "+x+"<<br />>"); fiddle(x); document.write("After function call x ="+x+"<<br />>");
In this situation, the function fiddle can change the values of the array held in the variable x, as shown here:
This is “pass by reference” in action. A pointer to the object is passed to the function rather than a copy of it.
Fortunately, unlike other languages such as C, JavaScript doesn’t force the user to worry about pointers or how to de-reference parameters. If you want to modify values within a function, just pass them within an object. For example, if you wanted to modify the value of a string in a function, you would wrap it in an Object:
function fiddle(arg1) { arg1.myString = "New value"; document.write("In function fiddle arg1.myString = "+arg1.myString+"<<br />>"); } var x = new Object(); x.myString = "Original value"; document.write("Before function call x.myString = "+x.myString+"<<br />>"); fiddle(x); document.write("After function call x.myString ="+x.myString+"<<br />>");
The result is
Of course, you could also use a return statement to pass back a new value instead.
One potentially confusing aspect of references is that references are passed by value. In computer science terms, this means that JavaScript references are pointers, not aliases. In less technical terms, this means that you can modify the value in the calling context but you cannot replace it. Assigning a value to a parameter that received a reference type will not overwrite the value in the calling context. For example:
function fiddle(arg1) { arg1 = new String("New value"); document.write("In function fiddle arg1 = "+arg1+"<<br />>"); } var x = new String("Original value"); document.write("Before function call x = "+x+"<<br />>"); fiddle(x); document.write("After function call x ="+x+"<<br />>");
At the beginning of fiddle, arg1 has a reference to the value of x:
It does not, however, have a reference to x itself. Assigning "New value" to arg1 replaces its reference to x’s data with a reference to a new string:
Since the assignment of "New value" isn’t a modification of x’s value, it is not reflected in x:
If this discussion went over your head, don’t worry; it’s rather advanced material. Just keep the following rule in mind: functions passed reference types can modify but not replace values in the calling context.