Page 1: A Simple Example

We’ll work up from the pieces, just to remember the JavaScript basics.

Functions in JavaScript

We want to write a program that responds when a button is pressed. This requires use to define a “handler” (or “callback”) function that is called when the button is clicked.

F-01-01.html and F-01-01.js are a very simple program that does something when the button is pressed.

Before trying it, read the code and make sure you can predict what it is going to do:

1
2
3
4
5
const button = document.getElementById("button1");
function handler() {
    button.innerHTML = "Changed!";
}
button.onclick = handler;

A few things to notice…

  1. Line 1 declares a const variable named button. We declare it as a const since we know it isn’t going to change. We could have made it a regular variable by using let instead. By declaring it as const we tell the compiler that it won’t change, so the compiler can warn us if we try to change it by mistake. It also provides documentation to the reader of the program that this variable won’t change value.
  2. The button variable is file scoped. You can think of it as a global variable (but global to this file).
  3. Line 2 declares function called handler. When the function is called, line 3 is executed.
  4. Line 3 is only executed when handler is called. It uses that “global” (file scoped) variable that we defined back on line 1. Functions have access to variables defined above them (assuming they aren’t in some local scope).
  5. Line 3 assigns the value “Changed !” to the innerHTML slot (member variable) of button (which we assume is an object that has an innerHTML slot).
  6. Line 5 assigns the handler function to the onclick slot of button.
  7. I just happened to know that if you assign a function to the onclick slot of a button, it will get called when the button is pressed.

That last step deserves some mention. We should think of the handler function as a special kind of object. Line 5 assigns that object (or a reference to that object) to the member variable.

Javascript lets us re-write line 2 in a different way that might make this clearer:

2
3
4
const handler = function() {
    button.innerHTML = "Changed!";
}

This is equivalent, but exposes what function declaration does. Think about declaring a function as the function keyword is a function that runs the compiler (on the code that follows it) and returns a “function object”. In this case, we assign that object to the variable handler.

Getting used to this idea that a function is just an object is important in JavaScript.

Because we are just using this function in one place, we don’t even need to both to assign it to a variable or give it a name. We can simply assign it directly to the slot where we need it:

1
2
3
4
const button = document.getElementById("button1");
button.onclick = function() {
    button.innerHTML = "Changed!";
}

The important thing here: functions are just objects, just like strings and numbers and buttons. They can be created, assigned to variables, passed as arguments to functions, …

Arguments

Let’s make an html file with two buttons (F-01-02.html), and extend the first example.

Here is that code applied to both buttons. Since I am using the function twice, it is useful to store it in a variable, although I could have written button2.onclick = button1.onclick.

1
2
3
4
5
6
7
const button1 = document.getElementById("button1");
const button2 = document.getElementById("button2");
const handler = function() {
    button1.innerHTML = "Changed!";
}
button1.onclick = handler;
button2.onclick = handler;

Again, understand what this program F-01-02.js does before trying it. It doesn’t do what you probably would want it to do.

Suppose we wanted the button clicked to change itself. We could define different functions for each button, that would be painful if we had lots of buttons, or if the code that the buttons had to run was complicated.

A better answer is to take advantage of the fact that handlers get an “event” object as an argument, and the “event” object know what html element is getting the event. So, F-01-03.html F-01-03.js will do the “right” thing.

The changed lines:

3
4
5
const handler = function(event) {
    event.target.innerHTML = "Changed!";
}

Show you that you that event handler functions receive an argument (that is the event), and that these events have target elements.

You might wonder, “why did the previous version work at all?” In the example above (F-01-02.js) the button click did pass the event argument to the handler function - the handler function just ignored it (by not taking any arguments).

A Gratuitous Closure

Warning: this section might not make sense at first. It will make more sense after the next few pages. The idea here is that you can see a closure, which will hopefully motivate you to understand why this works and how you can use it (which the following pages explain).

How could we have written the previous program if we didn’t know about events?

Here’s one way: (the code is F-01-04.js)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const button1 = document.getElementById("button1");
const button2 = document.getElementById("button2");

function setHandler(button) {
    const handler = function() {
        button.innerHTML = "Changed!";
    }
    button.onclick = handler;
}

setHandler(button1);
setHandler(button2);

The most obvious difference here is that we are defining a function setHandler that “sets up” a button. It takes a button as an argument, and sets the onclick slot of the button.

There are (at least) two things here that may be slightly weird:

  1. We define a function inside of the function. Line 5 effectively runs the compiler each time the setHandler function is called. Because setHandler is called twice (lines 11 and 12), the compiler will compile that code twice. A good compiler is probably smart enough that it probably doesn’t actually compile the code multiple times, but conceptually, you can think about the compiler being run twice.
  2. The inner handler function uses the button variable that belongs to setHandler. Arguments (like button) are just variables, and obey the same scoping rules.

This is a little tricky. Basically, for each button, we call setHandler to set it up. setHandler knows which button it is working on, because it has the button in its button parameter. setHandler runs the compiler and makes a new version of the handler function that it assigns to the button. This version has the right value for button.

When we ran the compiler on line 5, the value of button had an appropriate value. That value was “enclosed” inside of the function object that was created. This idea of enclosing variables when functions are defined is called closure.

If you need to be convinced that this works:

The important concept here is that when we define a function, that function has access to the variables “around it” (defined above and outside the function). You can access “global” (or file scoped variables). And, you can access variables in enclosing scopes.

Summary of Page 1

The lessons here:

  1. Think of functions as objects that are created, stored in variables, passed as function arguments - just like other objects.
  2. Think of the function keyword as something that runs the compiler on the code (while the script is executing) and produces a value (a function object)
  3. When a function object is created, it remembers the variables that enclose it. (it actually remembers the variables, not the values - but we’ll see that on a later page)

On the next page we’ll review the concept of scope to get a better sense of what is enclosed.

Next: Variables and Scope