Functions - Dynamic HTML5 Web Applications with JavaScript and jQuery - HTML5, JavaScript and jQuery (Programmer to Programmer) - 2015

HTML5, JavaScript and jQuery (Programmer to Programmer) - 2015

Part II Dynamic HTML5 Web Applications with JavaScript and jQuery

Lesson 13 Functions

Functions may seem simple in JavaScript, but beneath this simplicity lies enormous power. Gaining an understanding of this power is one of the keys to mastering the JavaScript language.

In Lesson 11, you created simple functions and invoked them from the console. For instance:

function isPositive(num) {

return num >= 0;

}

In JavaScript, functions are objects so it is possible to assign them to variables:

f1 = function isPositive(num) {

return num >= 0;

}

If you ask JavaScript the type of f1, it will respond as follows:

> typeof f1

"function"

This is another example of JavaScript being slightly disingenuous. Functions are not a distinct data-type; they are objects and therefore support all the features you will learn about in the next lesson, such as the ability to invoke methods on them.

Once you have assigned a function to a variable, you can invoke it via its variable name by appending brackets and parameters:

> f1(9)

true

In fact, you can use this variable wherever you can use any other variable in JavaScript; for instance, you can pass it as a parameter to another function.

Consider an example where you want to write a function that counts how many positive numbers are in an array. With the power of functions, you can do this by writing a generic algorithm as follows:

function countForArray(array, condition) {

var result = 0;

for (var i = 0; i < array.length; i++) {

var element = array[i];

if (condition(element)) {

result++;

}

}

return result;

}

This algorithm accepts an array and a function. It then loops through every element in the array and passes it to the function provided. If the function returns true, the count is incremented by one. You can then call this as follows:

> a = [1,2,-3,2,-5]

> countForArray(a, f1)

3

Notice that you are passing a reference to the function you defined earlier. It may not looked like you have gained much in this example; after all, the countForArray method could have very easily checked if the number was positive without using the function passed in.

The beauty of countForArray, however, is that it is a generic function that can be made to behave differently very easily. For instance, if you want to count the negative numbers you can use the following code:

> countForArray(a, function(num) {

return num < 0;

})

2

Notice that in this case you did not even create the function in advance; you simply declared it as part of the call to countForArray. The function that you have created does not even have a name; it is therefore called an anonymous function. Its scope is limited to the duration of this function call.

Once you have a function such as countForArray, you can use it for a whole variety of tasks that you may not even have thought about when you originally wrote it.

Functions that are passed to other functions are often called callback functions because they allow another function to “call back” to them at the appropriate time.

JavaScript arrays natively support a number of functions that can be used for performing common operations. For instance, one of the most common operations performed on arrays is to filter out a set of elements that do not meet a set of criteria. For instance, you might want to filter all the negative numbers out of an array, leaving only the positive numbers.

JavaScript arrays provide a filter method for performing this operation. Just like countForArray, this passes each element in the array in turn to a function provided, and retains those that return true:

> a.filter(f1)

[1, 2, 2]

Likewise, you can filter out positive numbers with the following code:

> a.filter(function(num) {

return num < 0;

})

[-3, -5]

You may have noticed that the filter function is not actually modifying the underlying array; instead, it is returning a new array with the relevant elements filtered out.

Another common operation performed on arrays is to transform each element in some way. With this particular array, you might want to transform the elements so that all the numbers are positive; this can be achieved with the map function.

The map function works in exactly the same way: You pass it a function, and the map function invokes it with each element in the array. The function you provide is responsible for modifying the element in some way and returning the modified version as a result.

The following returns an array of elements where each number has been converted to a positive value:

> a.map(function(num) {

return Math.abs(num);

})

[1, 2, 3, 2, 5]

Because functions such as map and filter both operate on arrays and return arrays, it is possible to chain together a whole set of function calls. Imagine that you want to return the absolute value of all even numbers in an array. This can be achieved as follows:

> a1 = [-2,1-3,5,6]

> a.filter(function(num) {

return num%2==0;

}).map(function(num) {

return Math.abs(num);

});

[2, 2, 6]

This example is a bit harder to follow, so start by breaking out its component parts. It starts out by performing a filter operation:

> a1.filter(function(num) {

return num%2==0;

})

[-2, -2, 6]

It then performs a map operation on the result: you can simulate this as follows:

> [-2, -2, 6].map(function(num) {

return Math.abs(num);

})

[2, 2, 6]

When writing JavaScript code it is often a good idea to think in terms of simple functions that perform a single task, and do not store or modify any global state. These functions can then be combined together to create more advanced functionality. Building software in this way tends to be simpler because it is very easy to understand, develop, and test each function in isolation.

Closures

Closures can be a difficult concept to explain, so I will explain them through examples.

Imagine a case where you want to write a function that can produce unique, incrementing numbers that can be used by other code in your web application. The only condition of this functionality is that if the last call to the function returned 10, the next call must return 11.

It is possible to write this functionality with a global variable:

> count = 0;

> function getNextCount() {

return count++;

}

> getNextCount()

0

> getNextCount()

1

As has already been mentioned, however, global variables should be avoided because any other code can modify them. For instance, any other code could reset the count variable:

count = -1;

or set it to a nonsense value:

count = 'hello';

These may look like contrived examples, but as web applications grow in size, global variables such as this become the source of difficult to find bugs. Closures provide an alternative.

Before looking at the solution, consider what happens to local variables inside a function when it finishes executing. The function that follows declares a local variable called myCount.

function counter() {

var myCount = 0;

return myCount++;

}

If you execute this and then attempt to access the myCount variable, you will find it does not exist:

> counter()

0

> myCount;

ReferenceError: myCount is not defined

The variable is created inside the function each time it is invoked, and it is automatically destroyed when the function completes. This is why the counter function always returns 0:

> counter()

0

> counter()

0

Now, consider this slight variation on the preceding function:

function getCounter() {

var myCount = 0;

return function() {

return myCount++;

}

}

Rather than returning a number, this function returns another function. The function that it returns has the following body:

function() {

return myCount++;

}

You can now assign a variable to refer to this function:

counter = getCounter();

There is something strange about this function though: It is referring to the local variable myCount that was defined inside the getCounter function. Based on my previous explanation, this should have been destroyed when the call to getCounter finished. Therefore you might expect that if you invoke the function returned by getCounter, it will fail.

Not only does it not fail, it gives you exactly the behavior you want:

> counter();

0

> counter();

1

The anonymous function created inside getCounter is referred to as a closure. When it is created, it “closes” over all the variables in scope at the time, and obtains a reference to them. When the call to getCounter finished, therefore, JavaScript recognized that the anonymous function still might need to use the myCount variable and did not destroy it.

Although the anonymous function can continue to use the myCount variable, it is completely hidden from all other code. This means that it is not possible for any other code to interfere with the value of this variable:

> myCount = 10;

10

> counter()

2

The preceding code created a global variable called myCount, but this does not have any impact on your counter, which continues to use the local variable of the same name.

In addition, if you were to create a second counter, it will have its own local myCount variable that will not impact your original counter. Instead, the new counter will also start counting from 0.

The beauty of this solution is that you have created private data. The function performing the counting is using a variable that only it has access to. This is an important technique in JavaScript because it does not support many of the mechanisms found in other languages for creating private data.

Hoisting and Block Scope

One interesting feature in JavaScript is the scope of variables inside functions. In most programming languages it is possible to declare variables within a sub-block (a loop for instance) and limit their scope to this block. Consider the following example:

function iterate(array) {

var count = 0;

for (var i = 0; i < array.length; i++) {

var count = 10;

}

return count;

}

If you were to invoke this function with an array, it would always return 10 because the count variable declared inside the for loop overwrites the count variable declared before the loop.

Part of the reason JavaScript operates in this manner is a concept called hoisting. Although it is possible to declare variables anywhere in a function, when JavaScript executes a function, it first searches for all the local variables in it and moves their declaration to the top of the function. They are, however, left undefined until they are explicitly given a value in the body of the function. In order to demonstrate this, create the following function:

function testHoisting() {

var num = num1 + num2;

var num1 = 10;

var num2 = 10;

return num;

}

If you call this function, you will notice it does not fail, even though it is using local variables before they are defined:

> testHoisting()

NaN

If you tried the same thing with global variables, however, the code will fail because global variables are not hoisted:

> function testHoisting() {

var num = num1 + num2;

num1 = 10;

num2 = 10;

return num;

}

> testHoisting()

ReferenceError: num1 is not defined

Arguments

As discussed many times, JavaScript functions can accept parameters. When you invoke a function, however, the number of arguments you pass does not need to be constrained by the number of parameters defined.

For instance, you can pass a single argument to a function accepting two parameters. In this case the second parameter will have a value of undefined.

Likewise, you can pass three arguments to a function accepting two parameters. This may not sound useful, but in fact JavaScript makes these arguments available in a special array called arguments.

Consider a case where you want to write a function to add together an arbitrary set of numbers. Obviously, you could pass an array to the function, but you can also write it as follows:

function add() {

var result = 0;

for (var i = 0; i < arguments.length; i++) {

result = result + arguments[i];

}

return result;

}

Notice that this function declares no parameters: Instead, it uses the arguments array to extract the arguments passed to it. It is now possible to call this function with an arbitrary number of arguments:

> add(3,7,8,10)

28

Bind

Since JavaScript functions are actually objects, it is possible to invoke methods on them. This section looks at a widely used method called bind.

You have already seen how functions “close” over all variables in scope when they are created. This set of variables can be thought of as the environment in which the function executes.

JavaScript is even more powerful than this: It is possible to provide the environment to the function in the form of an object, therefore allowing the function to use an entirely different set of variables.

Imagine that you want to create a counter that is capable of starting from any number, not just 0. One way to achieve this is to create the function as follows:

function getCount() {

return this.myCount++;

}

Notice in this case that you have not provided a starting value for myCount, and you are accessing the variable with this invoke getCount().myCount rather than just myCount. You will look at the meaning of this in the next lesson.

If you were to create a counter function, it would not work because it does not have a value for myCount.

You can instead bind this function to a new environment by providing a set of name/value pairs for the variables in the environment:

> var counter2 = getCount.bind({myCount:100});

undefined

> counter2()

100

> counter2()

101

Note

The set of name/value pairs inside curly brackets is actually an object:You will look at how objects can be constructed in a lot more detail in the next lesson.

As you can see, the bind function returns a new version of the function, permanently bound to the new environment. You can then invoke this function and obtain the appropriate results for the environment it is bound to.

Try It

In this Try It, you will start by writing a function that takes advantage of the techniques you have learned in this lesson. This function will implement a stand-alone version of the map method.

Next, you will look at another of the methods provided by arrays called reduce. This can be used to aggregate the values in an array—for instance, to sum them.

Lesson Requirements

In order to complete this lesson, you will need the Chrome web browser. You may, however, want to complete these exercises in a text editor and copy the results to the console.

Step-by-Step

1. Open the Chrome development tools and selecting the Console tab.

2. Define a function called map that accepts two parameters: an array and a function for performing the map operation.

3. In the body of the function, you first need to construct an empty array to hold the result of the function.

4. Use a for loop to iterate through all the elements in the array. Remember to use a counter variable and declare that the loop should continue while this counter is less than the length of the array.

5. Within the body of the for loop, extract the element that is at the position of the counter. Store this in a local variable.

6. Pass the element to the function provided in the second parameter, and store the result in another variable.

7. Add the mapped variable to the result array using the push method—for example, result.push(value).

8. In order to execute this function, start by creating an array that contains a mixture of odd and even numbers.

9. Create a function that accepts a single parameter. If this parameter is even (remember, you can use the modulus operator: %), it should simply be returned; if it is odd, add 1 to the number and return it. This function therefore converts all numbers into even numbers.

10.Assign the function to a variable.

11.Call the map function you created earlier with the array, and the variable referring to the function to convert numbers to even. The result should be an array of even numbers.

In the second section of this Try It, you will look at the reduce function. This is similar to map and filter, but slightly more complex. This function is used to aggregate the data in an array to a single value such as a sum or an average (the single value can also be an object or an array if required). This function is more complex because it needs to keep track of a running total as it executes.

1. Create an array of numbers that can be summed together.

2. Create a function that can be used for summing the numbers together. This should be called addToTotal and will accept two parameters, a current total and a new value to add to this total.

3. In the body of the function, return the sum of the two numbers.

4. Add logging to the addToTotal so you can see what is happening: print both parameters to the console.

5. You now want to call the reduce method on the array. This accepts two arguments, a function and an initial value for the aggregation: therefore pass in addToTotal and 0.

6. Remember that you are passing the function itself rather than calling it. Thus, when you pass addToTotal, you should only include its name; you should not call it with a set of parameters.

7. When I run this, it produces the following output:

8. [6, 2, 3].reduce( addToTotal, 0);

9. Current total:

10. Value: 6

11. -----------------

12. Current total:

13. Value: 2

14. -----------------

15. Current total:

16. Value: 3

17. -----------------

11

Reference

Please go to the book's website at www.wrox.com/go/html5jsjquery24hr to view the video for Lesson 13, as well as download the code and resources for this lesson.