More Basics - Getting Good with JavaScript (2011)

Getting Good with JavaScript (2011)

More Basics

Now that we've got the very basics of types and syntax down, let's move on to another incredibly important feature: functions. Pay close attention!

Functions

What is a function? A function is really just an encapsulated bunch of code. We've been looking at a bunch of JavaScript syntax: all of that can go inside functions, and often that's what you'll do. When you put code inside a function, it isn't run right away, like "loose" code. You decide when the code runs by calling the function, and you can do that as often as you want to. This is part of the benefit of functions.

But don't forget the encapsulation part: that means that at the time you invoke, or call, a function, you don't have any control (for the most part) of what goes on inside it. You'll hand the function some values to work with, and it will hand a value back; but what goes on inside it is black-boxed. This turns out to be an important thing that we can take advantage of, as we'll see later.

There's two other important things you should know about functions. First off, functions are first-class objects in JavaScript, which means that they are objects, and that they are no different from any other value. Let's take these two points a bit further.

Functions are Objects: We saw how to create a object literal earlier. Now, we can't create a function using that object literal notation, but a function can have properties and methods, because it is an object. You can add your own properties and methods to functions, but we'll look at some of the built in ones soon.

Functions are like other Values: In many programming languages, a function is something very different from primitive values like strings and numbers. However, in JavaScript, that's not the case. You can use functions in most places that you'd use another value, like passing it to another function (which we'll see shortly). Also, you can redefine a function, just like you'd redefine any other variable. This, as you'll see, is very handy.

Now that we understand a bit of function theory, let's look as some syntax.

Function Syntax

As we know, a function is just any number of lines of regular JavaScript. But we need to wrap that in function material. Let's say we have some code that determines an appropriate greeting for our website:

Example 3.1

var hour = 10,

message;

if (hour < 12) {

message = "Good Morning!";

} else if (hour < 17) {

message = "Good Afternoon!";

} else {

message = "Good Evening!";

}

console.log(message); // Good Morning!

We've created an hour variable, with the hypothetical current hour. Then, we use an if statement to figure out in what range our hour is, and assign message accordingly.

So, what do we do to convert this code to a function? Well, first, let's meet the function shell:

function getGreeting () {

// code goes here

}

Start with the keyword function; then, write the name of the function; it can be whatever you want, as long as it follows the rules for variable names (i.e. alphanumeric characters, underscore, and dollar sign, but not starting with a number). Next, parentheses. Then, the lines of code go between curly braces.

Now, you might be tempted to throw all the code we wrote above into a function shell. This isn't wise for two reasons. Firstly, there would be no way to get to the variable message (more on function scope soon). Secondly, with a bit of customization, we can increase the usefulness of the function by tweaking it a bit.

Let's make our function accept a parameter. A parameter is a value that you hand to the function when you ask the function to run. The function can then use the parameters however it needs to. How does this work syntactically?

function getGreeting (hour) {

//code here

}

Here, our getGreeting function accepts one parameter, which we're calling hour. Parameters are like variable that you can only access from within the function. To invoke this function and give it an hour, we would do this:

getGreeting( 10 );

We could store the hour in a variable, but we don't need to, since we won't need it after this.

So, we're passing the current hour to getGreeting. Now we're ready to write the code inside that function:

Example 3.2

function getGreeting(hour) {

var msg;

if (hour < 12) {

msg = "Good Morning!";

} else if (hour < 17) {

msg = "Good Afternoon!";

} else {

msg = "Good Evening!";

}

return msg;

}

var message = getGreeting( 16 ); // 4 PM

alert(message); // Good Afternoon!

If you've been following along carefully, there should be only one thing above that you're not sure about: return msg. What's up with that? To return a value is to pass it back to the code that called the function. That way, we can assign the calling of a function to a variable (as we do above), and it will be whatever value was returned from getGreeting.

Here are two more things I'd like to do to this function, to make it better. First, there's really no need for the msg variable, because we only ever do one thing to it. Try this:

Example 3.3

function getGreeting(hour) {

if (hour < 12) {

return "Good Morning!";

} else if (hour < 17) {

return "Good Afternoon!";

} else {

return "Good Evening!";

}

}

alert( getGreeting( 3 ) ); // Good Morning!

This makes it clear that we can have more than one return. Once we figure out which value we must return, we'll do so right away. I should note that a return statement will be the last line to be executed in a function; once the function returns, it's done; nothing else is executed. Therefore, unless your return statement is inside a conditional statement, you usually won't have any code after the return line in your function.

One more thing: When writing functions, you want them to be user-friendly. Let's do this: if the user doesn't pass in a parameter, we'll provide a reasonable default.

Example 3.4

function getGreeting(hour) {

hour = hour || new Date().getHours();

if (hour < 12) {

return "Good Morning!";

} else if (hour < 17) {

return "Good Afternoon!";

} else {

return "Good Evening!";

}

}

alert( getGreeting( new Date().getHours() ) );

What's going on here? We're using a logical OR operator; remember what this does: if the first side is true, it will use that; otherwise, it will use the second side. In this case, we're saying that we want to set the hour parameter to either the value of the hour parameter (if the user passed one in), or the current hour. We're using the getHours function of a current Date object; we'll see more about this in a page or so. If the user didn't pass a parameter, hour will be undefined, which is false. Therefore, the function will assign the current hour to hour. And yes, you can assign new values to parameters like this.

ROCKSTAR TIP

This is the best way to provide default values for parameters. It's a pretty advanced JavaScript tip, but I think you can handle it. Don't feel bad if it's a bit complex, though. Come back to it later and you should have no troubles.

Well, that's out first function. Let's wrap up with discussion on functions with a few important points.

Arguments

We've been calling the values that you pass to a function parameters. There's another common term, and that's arguments. In fact, JavaScript itself calls the them arguments. Many other programming languages have error-detection features related to the arguments of a function: they make sure that when calling a function you pass in the right number and type of arguments. JavaScript doesn't offer any of this. These are all perfectly valid ways of calling our getGreeting function above:

var msg1 = getGreeting(10),

msg2 = getGreeting(1,2,3,4,5,6, "and on"),

msg3 = getGreeting(),

msg4 = getGreeting("five o'clock");

And there are many more, of course. Out of all these situations, we've only written our function to work with options 1 and 3. However, it will work with option 2: hour will be set to 1, but all the following arguments will be lost. Option 4 is a good example of why some error checking is necessary: to guard against string parameters in our function, we should use the typeof operator to make sure it's a number (after we assert that they did indeed give us a parameter). If it's not a number, we should provide the default: the current hour.

I said that for option 2 above, all the extra arguments are lost. Well, that's not exactly true. JavaScript does provides a way to get to them: it offers an object called arguments that you can access from inside your functions. You might think that it would make more sense if arguments was an array … and every other JavaScript programmer would agree with you. However, it's an array-like object which means it has some of the characteristics of an array, but not all of them. It has a length property, which is the number of arguments passed into the function. You're also able to access the parameters with square bracket notations, just like in an array. There are no other similarities with a real array. That is all.

So we could write a sum function like this: we loop over each argument that was passed in and add it to the variable sum. Then, we return sum.

Example 3.5

function sum() {

var sum = 0, i;

for ( i = 0 ; arguments[i]; i++) {

sum += arguments[i];

}

return sum;

}

var sum1 = sum(1, 2),

sum2 = sum(100, 75, 40),

sum3 = sum(9, 1.4, 8, 2, 79, 3234, 6, 4, 5e3, 5);

alert(sum1); // 2

alert(sum2); // 215

alert(sum3); // 8348.4

Scope

We mentioned scope in passing (quite) a few paragraphs ago. Let's discuss that in more detail now. While scope isn't related to functions only, functions are a main part of scope in JavaScript, so this is appropriate timing.

Scope is basically the set of variables that you have access to at a given point in your code. JavaScript has function scope, which means that the scope changes when we enter a function. Let's look at a few examples.

Let's start with the global area. If you write

var name = "Bob";

at the top level of a JavaScript file—meaning it's not inside a function—that variable will be accessible from everywhere within the JavaScript environment. Most often, that environment will be a webpage; this means that every other JavaScript file on the page will have access to that namevariable from everywhere within the file. Any variables created in this "global namespace" will have global access-ability.

ROCKSTAR TIP

You might think that having access to variables globally is a great thing. With that, you don't ever have to worry about whether or not you can get to the value you need—because you always can! This is emphatically not the case . . . and we'll talk more about why that is in the next chapter.

As I mentioned, the scope changes when we enter a function, and only when we enter a function (this isn't true of most other languages). We still have access to all the variables outside the function, but now we have a group of variables that can be accessed from only inside this function.

For example,

Example 3.6

var name = "Bob";

function greet (greeting) {

var punc = "!!!";

return greeting + " " + name + punc;

}

alert( greet("Hello") ); // "Hello Bob!!!"

This rather simple example demonstrates function scope. The name variable is global, so we can reach it from inside the function. Arguments of the function, like greeting, are part of the function's scope, so there's no way to access that outside the function. Also part of the function's scope are any variables created inside the function: in this case, that's punc.

This will blow your mind: we can nest functions in JavaScript. Nested functions are just like their parent functions, in that they have access to all the scopes "above" them, as well as their own:

Example 3.7

var name = "Bob";

function greet(greeting) {

function get_punc() {

return (greeting === "Why") ? "?" : "!";

}

return greeting + " " + name + get_punc() + " " + greeting + get_punc();

}

alert( greet("Why") ); // "Why Bob? Why?"

alert( greet("Hi") ); // "Hi Bob! Hi!"

Another example that hopefully points out the scope of nested function; the inner function can access the greeting argument of its parent function, and if it needed to it could access name as well. As you might guess, a function inside a function is useful when you need to execute the same code more than once during the execution of a function: just put that code in a function and call it whenever you need to.

Now that you're understanding that, chew on this for a while:

Example 3.8

var name = "Bob";

function greet(greeting) {

var name = "Alice";

return greeting + " " + name + ".";

}

alert( greet("Hello") ); // "hello Alice."

What does this return? The name variable inside the function "overwrites" our access to the outside name variable. Now, when we use name, it refers to Alice, not Bob. However, once we're back outside the function, name will once again refer to Bob, because there's no way for us to get to Alice outside of her function.

Anonymous Functions

Thus far, the functions we've made all have names. However, we can create functions without names, just be leaving the name out:

function () {

return "this function has no name";

}

Well, cool, I guess, you're saying. But what's the point? There's no way to call it. Well, we'll see many places where anonymous functions (for that is what nameless functions are) are useful; here's one that you might use often:

Example 3.9

var hi = function () {

return "Hi";

};

alert( hi() ); // Hi

Yes, that's right; we can assign a function (named or anonymous) to a variable, and execute it with that variable name. Remember, functions are objects and can be assigned to variables just like values.

Here's another use for anonymous functions:

var hi = (function () {

return function () {

return "Hi";

}

}());

Take a deep breath; this might look like rocket science, but there's not that much going on here. Start with what you know: we're assigning an anonymous function to the variable hi. That anonymous function returns another anonymous function. That's all good. Then, we're wrapping the first function in parentheses. This isn't required, but you can wrap pretty much anything you want to in parentheses in JavaScript; it just improves readability. In this case, we do it to remind ourselves that we're doing something else of interest: notice that, after the function, we have a set of parentheses. We know that a set of parentheses after a function name executes that function. Well, they do the same after a function itself: function () { /**/ }(). This is called an anonymous self-invoking function, because it has no name and it executes itself. Since it runs right away, hi is not assigned that function, but the function's return value: another function! In this case, it's just like doing this:

var hi = function () {

return "Hi";

};

So why not do that? Well, this is just an example. We'll see a real use for this pattern—assigning the returned value of anonymous self-invoking to a variable—later (if you can't wait, read the section on closure, on page 80).

I should note that, while this pattern is usually called an anonymous self-invoking function, not everyone agrees with that term. You'll see that name a lot, but Ben Alman—frequent contributor to jQuery and creator of many popular JavaScript projects—prefers the term immediately-invoked function expressions. You can read more about why he prefers that term in his blog post on the topic.

Type Methods

In the last chapter, we learned about the value and reference types that JavaScript offers us. Let's conclude by getting to know the methods of these types. Of course, we won't learn them all, but we'll cover all the important ones, the ones you'll use regularly.

ROCKSTAR TIP

What's the difference between a function and a method? Only where they exist. A method is just a function that is a property of an object. For example:

var console = {

log : function () {

// code here

}

};

You'd call this the same way you access "normal" properties: `console.log()`. Look familiar?

String Methods

length

This one is actually a property, not a method. Predictably, the length property of a string is the number of characters in the string.

Example 3.10

alert( "gobsmacked".length ); // 10

indexOf

This is the method you'll most often use when you're searching within a string. This method returns a number, the index of your "search term."

Example 3.11

var line = "HAL: I'm sorry, Dave. I'm afraid I can't do that";

alert( line.indexOf("I'm") ); // 5

alert( line.indexOf("I'm", 6) ); // 22

You can pass a second parameter as the starting point, if you don't want to start searching at the beginning of your string. Remember, these indices are zero-based—just like the indices of an array—which means that the first letter of the string is indexed 0, the second is 1, and so on. If the substring you searched for doesn't exist, you'll get -1 back.

slice, substr, and substring

That's right; there are three methods for getting part of a string out of a larger string. First, you can use slice. It takes two parameters: the first parameter is the starting index, and the second one is the ending index, meaning the index of the character after the last character in the desired substring. If you leave the second one off, it will slice until the end of the string.

Example 3.12

var greeting = "Hello, Andrew, what's up?",

name = greeting.slice(7, 13);

alert(name); // Andrew

These index parameters can also be negative numbers, which means they "count" from the end of the string. This way, -1 is the last item in the string, -2 is the second last, and so on.

An alternative to slice is substr. The first parameter is the same as slice—the starting index—but the second parameter is the length of the substring:

Example 3.13

var greeting = "Hello, Andrew, what's up?",

name = greeting.substr(7, 6);

alert(name); // Andrew

Since you're string can't have a negative length, you can't use a negative number for the second parameter (you can use a negative number for the first parameter, though). Of course, that second parameter is optional, if you want the rest of the string.

Finally, there's substring. This works similarly to slice, except when it comes to negative values. A negative value for either parameter acts as 0—it refers to the start of the string. The neat thing about substring is that, unlike slice, the second parameter can be lower than the first (while still positive). When this is the case, substring goes back to the character of that index. For example,

Example 3.14

var greeting = "Hello, Andrew, what's up?",

name = greeting.substring(13, 7);

alert(name); // Andrew

split

It's easy to pull a string into an array with the split method. Just pass the method a parameter determining what character to split the array on.

Example 3.15

var arr = "apples oranges peaches bananas".split(" ");

console.log(arr); // ["apples", "oranges", "peaches", "bananas"]

Yes, this is the way lazy programmers create arrays. But it has more use than just as an alternative to an array literal.

toLowerCase and toUpperCase

I'm sure you know exactly what these do: easy converting of a string to upper- or lowercase:

Example 3.16

var usa = "usa".toUpperCase(),

comment = "THIS MIGHT BE A COMMENT";

comment = comment.substr(0,1) + comment.slice(1).toLowerCase();

console.log(usa); // USA

console.log(comment); // This might be a comment

Numbers

toExponential

If you'd like to convert a number to its exponential form, this is the method to use. It takes one parameter: the number of decimals places to use. If you leave the parameter out, JavaScript will make sure your number isn't rounded.

Example 3.17

alert( 12345..toExponential() ); // "1.2345e+4", meaning 1.2345 x 10^4

alert( 987.65432.toExponential(3) ); // "9.877e+2"

alert( 0.1234.toExponential() ); // "1.234e-1"

If your scratching your head at the double-period in the first example above, a short explanation is in order. When JavaScript sees a dot after a number, it expects more digits; it thinks it's a decimal point. So writing 5 is translated as 5.0. So if we wrote 5.toExponential(), we'd get an error. We could do 5.0.toEponential() if we wanted to, or you could save the character and just do the double-dot. Don't worry, in most cases, your numbers will probably be in variables, so it won't make to much of a difference. (You could also wrap the number in parentheses:(5234).toExponential());

toFixed

This method will round your numbers to the number of decimal points you define in the parameter:

Example 3.18

alert( 6..toFixed(4) ); // "6.0000"

alert( 3.14159.toFixed(2) ); // "3.14"

alert( 10.46.toFixed(1) ); // "10.5"

You'll find this useful if you're working with money, and need to round to the nearest cent.

toPrecision

This method is for choosing how many digits should represent your number. This includes numbers of both sides of the decimal point:

Example 3.19

alert( 15.7896.toPrecision(4) ); // "15.79"

alert( 1940..toPrecision(2) ); // "1.9e+3"

Wait, what? How does that last example work? Well, it round the number a precision of 2, which in this case is to the hundreds digit. Then, it converts it to exponential notation.

I'll add here that all three of these number methods don't changed the variable with the number; they return a new value that you can assign to another (or the same) variable:

Example 3.20

var a = 300,

b = a.toFixed(2);

console.log(a, typeof a); // 300

console.log(b, typeof b); // "300.00"

You may also have noticed that all three of these methods return strings, not numbers. What are you to do if you want to convert them back to numbers? You can use the global function parseInt and parseFloat. You use parseInt when you want to get a whole number (parse integer). You can hand it a string, and if it can make sense of a number within it, it will do so. For example:

Example 3.21

alert( parseInt("123", 10) ); // 123

alert( parseInt("45.67", 10) ); // 45

alert( parseInt("$12.00", 10) ); // NaN

parseInt will start at the beginning of the string and stop when the character isn't a number. That's why you get "Not a Number" from a string starting with "$." The second parameter is the radix: the base of the number system we want the string to be parsed to. So 10 is decimal, 2 is binary, 8 is octal, and so on.

parseFloat can take decimals and exponential forms:

Example 3.22

alert( parseFloat("45.67", 10) ); // 45.67

alert( parseFloat(1940..toPrecision(2), 10) ); // 1900

Just like parseInt, parseFloat stops when the character is not a number, decimal point, or exponential notation.

Date Methods

There are a ton of methods for Date objects, but some are used way more often than others.

get____

You'll probably use the family of get_____ methods most often, for getting different pieces of the date. Here are the specifics; you'll notice that most of the values are zero-based:

Method Name

Return Value

getDate

day of month, 1 - 31

getDay

day of week, 0 - 6

getFullYear

year

getHours

hour, 0 - 23

getMilliseconds

milliseconds, 0 - 999

getMinutes

minutes, 0 - 59

getMonth

month, 0 - 11

getSeconds

seconds, 0 - 59

getTime

milliseconds since January 1, 1970

getTimezoneOffset

difference between GMT and local time, in minutes

set____

Most of the get methods have a corresponding set method. They each take a single parameter, identical to the return value of their get counterpart. Here are your options:

· setDate

· setFullYear

· setHours

· setMilliseconds

· setMinute

· setMonth

· setSeconds

· setTime

parse

Sometimes you'll have a string date that you want to convert to a Date object. Date.parse will do that for you. It actually converts it to a number, the number of milliseconds since midnight on January 1, 1970 (known as the Unix epoch). Then, you can plug that number into new Date() to get a Date object.

Example 3.23

alert( Date.parse("June 18, 1970") ); // 14529600000

alert( Date.parse("2010/11/11") ); // 1289451600000

var d = new Date(Date.parse("1995/04/25")); // Tue Apr 25 1995 00:00:00 GMT-0400 (EST)

alert(d);

Array Methods

Arrays have useful methods as well. Let's check out some of the common ones!

join

This is the reverse of the string's split method. It will join all the elements in the array into a string. You can pass a single parameter to join that will be put between each element in the array.

Example 3.24

alert( ["cats", "dogs", "hamsters", "fish"].join(' ') ); // "cats dogs hamsters fish"

alert( "this should not have spaces".split(" ").join("_") ); // "this_should_not_have_spaces"

Bonus tip here: notice what I did in the last example: I called the join method right off the split method call. How's this work? Well, we know the split method returns an array. So we can call a method on that returned array immediately; we don't have to save it to a variable first. This is called chaining methods, and there's no limit to how long you can make the method chain: as long as the return value of one method has the next method in your chain, you're gold. Many JavaScript frameworks (like jQuery) take advantage of this ability.

pop / shift

These two methods remove and return one value from the array. pop gets the last item, shift gets the first item.

Example 3.25

var arr = ["cats", "dogs", "hamsters", "fish"];

console.log( arr.pop() ); // "fish"

console.log( arr.shift() ); // "cats"

console.log( arr ); // ["dogs", "hamsters"]

push / unshift

Predictably, these are the opposite of pop and shift. push adds an item to the end of the array, and unshift to the beginning.

Example 3.26

var arr = ["Beta", "Gamma"];

arr.push("Delta");

console.log(arr); // ["Beta", "Gamma", "Delta"]

arr.unshift("Alpha");

console.log(arr); // ["Alpha", "Beta", "Gamma", "Delta"]

Both return the number of items in the array after the new item is pushed in.

reverse

Just guess what it does. Yes, it returns an array with the items reversed.

Example 3.27

alert( ["Crockford", "Resig", "Zakas"].reverse() ); // "Zakas, Resig, Crockford"

slice

Sometimes you'll want to slice your array into multiple parts. This is your method. It takes two parameters: the (zero-based) index of the starting element and the index of the element after the last one you want to slice. You can leave the second parameter off to select the rest of the array. You'll get the sub-array back:

Example 3.28

alert( ["a", "b", "c", "d", "e"].slice(2, 4) ); // ["c", "d"]

alert( ["f", "g", "h", "i", "j"].slice(1) ); // ["g", "h", "i", "j"]

sort

The sort method will re-order the items in your array however you'd like. If you don't pass in any parameters, JavaScript will sort the elements alphabetically.

Example 3.29

["d", "e", "c", "a", "b"].sort(); // ["a", "b", "c", "d", "e"]

Unfortunately, it sorts numbers "alphabetically," too, which means that the array [0, 5, 10, 15, 20, 25] will be sorted to [0, 10, 15, 20, 25, 5]. Not good. Instead, you can pass a sorting function into sort. That function should take two parameters—say, a and b—which will be items from the array. Here's how it works: the sort method will pass the first and second items of the array to the function as the parameters; then it will pass the second and third items, and so on. Inside the sorting function, you must compare the two items. Then, return one of these values (assuming the first parameter is a and the second, b):

· If a should come before b, return a negative number.

· If a and b are equal, return 0.

· If b should come before a, return a positive number.

If you're just sorting numbers, this is really easy:

Example 3.30

var arr = [5, 2, 3, 4, 1];

arr.sort(function (a, b) {

return a - b;

});

console.log(arr); // [1, 2, 3, 4, 5];

For more on sorting functions, check out this tutorial I wrote on Nettuts+ a while back.

Math functions

JavaScript also has a Math object; you can't create objects from it, like with Date, but it offers some useful mathematic functionality.

min

A helpful function that returns the lowest of the number parameters you pass in:

Example 3.31

alert( Math.min(9, 1, 4, 2) ); // 1

max

Yes, it's the opposite of min:

Example 3.32

alert( Math.max(9, 1, 4, 2) ); // 9

random

This function returns a random number between 0 and 1. What good is that? Well, you can then multiply it by 10 to get a number between 0 and 10, roughly. Anytime you need a random number, this is your tool:

Example 3.33

alert( Math.random() ); // 0.5938208589795977 (you'll probably get something else)

alert( Math.random() * 10 ); // 6.4271276677027345 (again, your mileage may vary)

round / ceil / floor

Math.round is your friend when you want to round a number to the nearest whole number. If you need to round up, use Math.ceil. Rounding down? Math.floor is here to help.

Example 3.34

alert( Math.round(10.4) ); // 10

alert( Math.round(10.5) ); // 11

alert( Math.ceil(10.4) ); // 11

alert( Math.floor(10.5) ); // 10

Combining Math.random and Math.floor, we can write a function that will return a random number between a minimum and maximum number:

Example 3.35

function getRandomNumberInRange(min, max) {

return Math.floor( Math.random() * (max - min + 1) + min);

}

alert( getRandomNumberInRange(0, 100) ); // 39; you'll probably get something different.

pow

This is the function to use when taking the power of a number. The first parameter is the number you're taking the power of and the second parameter is the power.

Example 3.36

alert( Math.pow(2, 3) ); // 8

alert( Math.pow(2, -1) ); // 0.5

Summary

Well, it's been a long haul, but you've pretty much mastered all the basics; you're familiar with all the data types built into JavaScript, and all their methods. You're also proficient using function and control structures (loops and conditionals). With the basics under your belt, we can now move on to some slightly more difficult techniques.