Chapter C

JavaScript

Bringing interaction to web pages since 1995.

Subsections of JavaScript

Introduction

As the World Wide Web was gaining popularity in the mid-nineties, browser manufacturers started experimenting with interpreting program scripts embedded within webpages. By far the most successful of these was JavaScript, initally developed by Brandon Eich for Netscape.

Brandon Eich Brandon Eich

Brandon Eich was hired to integrate the Scheme programming langauge into the Netscape browser. But when Netscape cut a deal with Sun Microsystems to bring Java Applets to their browser, his mission was altered to create a more Java-like langauge. He developed a prototype in only ten days, that blended Java syntax, the Self object-orientation approach, and Scheme functionality.

Netscape eventually submitted JavaScript to ECMA International , resulting in the ECMAScript standard, and opening the door for other browsers to adopt JavaScript. Currently all major browsers support the full ECMAScript 5 standard, and large chunks of ECMAScript 6 and some parts of later versions as well. Moreover, transpilation can be utilized to make newer ECMAScript-compliant code run on older browser versions.

Info

The ECMA standard is maintained by ECMA International , not the W3C. However, its development process is very similar, involving stakeholders developing proposals for new and improved features.

Basic Syntax

CONSOLE

Because Netscape was adopting Java at the same time they were developing what would become JavaScript, there was a push to make the syntax stay somewhat consistent between the two languages. As a result, JavaScript has much of the look and feel of an imperative language like C, C#, or Java.

However, this similarity can be deceptive, because how JavaScript operates can be quite different than those languages. This can lead to frustration for imperative programmers learning JavaScript. As we go over the basics of the language, I will strive to call out these tricky differences.

To assist you in learning JavaScript syntax, we’ve added an interactive console to this textbook where you can type in arbitrary JavaScript code and see the result of its execution, much like the console that Developer Tools provide. You can click the word “Console” on the purple tab below to expand it, and click it again to minimize it.

Interpreted Language

JavaScript is an interpreted language, which means instead of being compiled into machine code, it is interpreted by a special program - an interpreter. Each browser has its own interpreter implementation.

Let’s start with a traditional example:

console.log("hello world");

Copy/paste or type this code into the console provided at the bottom of the page. What is the output?

As you might expect, this prints the string “hello world” to standard output. Notice we didn’t need to put this code into a main function - JavaScript code is executed as it is encountered by the interpreter.

Terminating Lines of Code

Also, the semicolon is an optional way to end an expression. A new line is other way to do so, so these two programs:

console.log("Hello")
console.log("World")

and

console.log("Hello");console.log("World");

are equivalent. We can also use both a semicolon and a new line (as in the first example). A common technique for making JavaScript files smaller, known as minifying takes advantage of this fact to write an entire program in a single line! We’ll discuss how and when to do so later.

Data Types

Like any programming language, JavaScript has a number of predefined data types. We can also query the data type of a value at runtime, using the typeof keyword. Try typing some of these lines into the console:

typeof 5;
typeof 1.3;
typeof "Hello";
typeof true;

Numbers

Numbers include integers and floats, though JavaScript mostly uses the distinction for how a value is stored in memory and presents the programmer with the number type. This category also includes some special values, like NaN (not a number) and Infinity. We can perform all the standard arithmetic operations on any number (+, -, *, /).
These operations are also “safe” in the sense that they will not throw an error. For example, try typing 4/0 in the terminal below. The value you see as a result is still a number!

The JavaScript interpreter will switch between an integer and float representation internally as it makes sense to. For example, type 4.0 and you’ll see the console echoes 4 to you, showing it is storing the number as an integer. Try typing 4.1, and you’ll see it stores it as a float.

Strings

The string type in JavaScript can be declared literally using single (') or double (") quotes, and as of ES6, tick marks (`).

Double and single-quoted strings work exactly the same. They must be on the same line, though you can add newline characters to both using \n. The backslash is used as an escape character, so to include it in a string you must use a double-backslash instead \\. Finally, in a single-quoted string you can escape a single quote, i.e. 'Bob\'s Diner', and similarly for double-quotes: "\"That's funny,\" she said." Judicious choices of single-or double-quoted strings can avoid much of this complication.

You can also directly reference unicode characters with \u[ref number]. Try typing the sequence "\u1F63C".

Finally, strings enclosed with tick marks (`) are template literals that have a few special properties. First, they can span multiple lines, i.e.:

`This is a 
multiline string
example`

The line breaks will be interpreted as new line characters. Secondly, you can embed arbitrary JavaScript inside of them using ${}. Give it a try:

`The sum of 2 and 3 is ${2 + 3}`
Info

In JavaScript there is no character type. In practice, the role characters normally play in programs is filled by strings of length one.

Booleans

JavaScript also has the boolean literals true and false. It also implements the boolean logical operators && (logical and) || (logical or), and ! (logical not).

Undefined

JavaScript has a special value undefined that means a value hasn’t been set. You probably saw it when you entered the console.log("Hello World") example above, which spit out:

> Hello World!
> undefined

As the console echoes the value of the prior line, it was printing the return value of console.log(). Since console.log() doesn’t return a value, this results in undefined.

Null

JavaScript also defines a null type, even though undefined fills many of the roles null fills in other languages. However, the programmer must explicitly supply a null value. So if a variable is null, you know it was done intentionally, if it is undefined, it may be that it was accidentally not initialized.

Objects

The object type is used to store more than one value, and functions much like a dictionary in other languages. Objects can be declared literally with curly braces, i.e.:

{
  first: "Jim",
  last: "Hawkins",
  age: 16
}

An object is essentially a collection of key/value pairs, known as properties. We’ll discuss objects in more depth in the Objects and Classes section.

Symbols

Finally, the symbol type is a kind of identifier. We’ll discuss it more later.

Variables

JavaScript uses dynamic typing. This means the type of a variable is not declared in source code, rather it is determined at runtime. Thus, all variables in JavaScript are declared with the var keyword, regardless of type:

var a = "A string";  // A string
var b = 2;           // A number
var c = true;        // A boolean

In addition, the type of a variable can be changed at any point in the code, i.e. the statements:

var a = "A string";
a = true; 

is perfectly legal and workable. The type of a, changes from a string to a float when its value is changed.

In addition to the var keyword, constants are declared with const. Constants must have a value assigned with their declaration and cannot be changed.

Finally, ECMA6 introduced the let keyword, which operates similar to var but is locally scoped (see the discussion of functional scope for details).

Type Conversions

JavaScript does its best to use the specified variable, which may result in a type conversion. For example:

"foo" + 3

Will result in the string 'foo3', as the + operator means concatenation for strings. However, / has no override for strings, so

"foo" / 3

Will result in NaN (not a number).

Additionally, when you attempt to use a different data type as a boolean, JavaScript will interpret its ’truthiness’. The values null, undefined, and 0 are considered false. All other values will be interpreted as true.

Control Structures

JavaScript implements many of the familiar control structures of conditionals and loops.

Warning

Be aware that variables declared within a block of code using var are subject to function scope, and exist outside of the conditional branch/loop body. This can lead to unexpected behavior.

If Else Statements

The JavaScript if and if else statements look just like their Java counterparts:

if(<logical test>) {
  <true branch>
}
if(<logical test>) {
  <true branch>
} else  {
  <false branch>
}

Loops

As do while and do while loops:

while(<logical test>) {
  <loop body>
}
do {
  <loop body>
}(<logical test>);

And for loops:

for(var i = 0; i < 10; i++) {
  <loop body>
}

JavaScript also introduces a for ... in loop, which loops over properties within an object. I.e.:

var jim = {
  first: "Jim",
  last: "Hawkins",
  age: 16
}
for(key in jim) {
  console.log(`The property ${key} has value ${jim[key]}`);
}

and the for ... of which does the same for arrays and other iterables:

var fruits = ["apple", "orange", "pear"];
for(value of fruits) {
  console.log(`The fruit is a ${value}`);
}

Try writing some control structures.

JavaScript Functions

CONSOLE

While JavaScript may look like an imperative language on the surface, much of how it behaves is based on functional languages like Scheme. This leads to some of the common sources of confusion for programmers new to the language. Let’s explore just what its functional roots mean.

JavaScript implements first-class functions , which means they can be assigned to a variable, passed as function arguments, returned from other functions, and even nested inside other functions. Most of these uses are not possible in a traditional imperative language, though C# and Java have been adding more functional-type behavior.

Defining Functions

Functions in JavaScript are traditionally declared using the function keyword, followed by an identifier, followed by parenthesized arguments, and a body enclosed in curly braces, i.e.:

function doSomething(arg1, arg2, arg3) {
  // Do something here...
}

Alternatively, the name can be omitted, resulting in an anonymous function:

function (arg1, arg2, arg3) {
  // Do something here...
}

Finally ES6 introduced the arrow function syntax, a more compact way of writing anonymous functions, similar to the lambda syntax of C#:

(arg1, arg2, arg3) => {
  // Do something here...
}

However, arrow function syntax also has special implications for scope, which we will discuss shortly.

Invoking Functions

Functions are invoked with a parenthetical set of arguments, i.e.

function sayHello(name) {
  console.log(`Hello, ${name}`);
}

Go ahead and define this function by typing the definition into your console.

Once you’ve done so, it can be invoked with sayHello("Bob"), and would print Hello, Bob to the console. Give it a try:

Functions can also be invoked using two methods defined for all functions, call() and apply() .

Function Arguments

One of the bigger differences between JavaScript and imperative languages is in how JavaScript handles arguments. Consider the hello() function we defined above. What happens if we invoke it with no arguments? Or if we invoke it with two arguments?

Give it a try:

sayHello()
sayHello("Mary", "Bob");

What are we seeing here? In JavaScript, the number of arguments supplied to a function when it is invoked is irrelevant. The same function will be invoked regardless of the arity (number) or type of arguments. The supplied arguments will be assigned to the defined argument names within the function’s scope, according to the order. If there are less supplied arguments than defined ones, the missing ones are assigned the value undefined. And if there are extra arguments supplied, they are not assigned to a value.

Can we access those extra arguments? Yes, because JavaScript places them in a variable arguments accessible within the function body. Let’s modify our sayHello() method to take advantage of this knowledge, using the for .. of loop we saw in the last section:

function sayHello() {
  for(name of arguments) {
    console.log(`Hello, ${name}`);
  }
}

And try invoking it with an arbtrary number of names:

sayHello("Mike", "Mary", "Bob", "Sue");
Warning

JavaScript does not have a mechanism for function overloading like C# and Java do. In JavaScript, if you declare a second “version” of a function that has different named arguments, you are not creating an overloaded version - you’re replacing the original function!

Thus, when we entered our second sayHello() definition in the console, we overwrote the original one. Each function name will only reference a single definition at a time within a single scope, and just like with variables, we can change its value at any point.

Finally, because JavaScript has first-order functions, we can pass a function as an argument. For example, we could create a new function, greet() that takes the greeter’s name, a function to use to greet others, and uses the arguments to greet an arbitrary number of people:

function greet(name, greetingFn) {
  for(var i = 2; i < arguments.length; i++) {
    greetingFn(arguments[i]);
  }
  console.log(`It's good to meet you.  I'm ${name}`);
}

We can then use it by passing our sayHello() function as the second argument:

greet("Mark", sayHello, "Joe", "Jill", "Jack", "John", "Jenny");

Note that we don’t follow the function name with the parenthesis (()) when we pass it. If we did, we’d inovke the function at that point and what we’d pass was the return value of the function, not the function itself.

Return Values

Just like the functions you’re used to, JavaScript functions can return a value with the return statement, i.e.:

function foo() { 
  return 3;
}

We can also return nothing, which is undefined:

function bar() {
  return;
}

This is useful when we want to stop execution immediately, but don’t have a real return value. Also, if we don’t specify a return value, we implicity return undefined.

And, because JavaScript has first-order functions, we can return a function:

function giveMeAFunction() {
  return function() {
    console.log("Here I am!")
  }
}

Function Variables

Because JavaScript has first-order functions, we can also assign a function to a variable, i.e.:

var myFn = function(a, b) {return a + b;}

var greetFn = greet;

var otherFn = (a, b) => {return a - b;}

var oneMoreFn = giveMeAFunction();

Functional Scope

We’ve mentioned scope several times now. Remember, scope simply refers to where a binding between a symbol and a value is valid (here the symbol could be a var or function name). JavaScript uses functional scope, which means a new scope is created within the body of every function. Moreover, the parent scope of that function remains accessible as well.

Consider the JavaScript code:

var a = "foo";
var b = "bar";

console.log("before coolStuff", a, b);

function coolStuff(c) {
  var a = 1;
  b = 4;
  console.log("in coolStuff", a, b, c);
}
coolStuff(b);

console.log("after coolStuff", a, b);

What gets printed before, in, and after coolStuff()?

  1. Before we invoke coolStuff() the values of a and b are "foo" and "bar" respectively.
  2. Inside the body of coolStuff():
  • The named argument c is assigned the value passed when coolStuff() is invoked - in this case, the value of b at the time, "bar".
  • A new variable a is declared, and set to a value of 1. This a only exists within coolStuff(), the old a remains unchanged outside of the function body.
  • The value of 4 is assigned to the variable b. Note that we did not declare a new var, so this is the same b as outside the body of the function.
  1. After the function is invoked, we go back to our original a which kept the value "foo" but our b was changed to 4.

That may not seem to bad. But let’s try another example:

var a = 1;
function one() {
  var a = 2;
  function two() {
    var a = 3;
    function three() {
      var a = 4;
    }
    three();
  }
}

Here we have nested functions, each with its own scope, and its own variable a that exists for that scope.

Block Scope

Most imperative programming langauges use block scope, which creates a new scope within any block of code. This includes function bodies, but also loop bodies and conditional blocks. Consider this snippet:

for(var i = 0; i < 10; i++) {
  var j = i;
}
console.log(j);

What will be printed for the value of j after the loop runs?

You might think it should have been undefined, and it certainly would have been a null exception in an imperative language like Java, as the variable j was defined within the block of the for loop. Because those languages have block scope, anything declared within that scope only exists there.

However, with JavaScript’s functional scope, a new scope is only created within the body of a function - not loop and conditional blocks! So anything created within a conditional block actually exists in the scope of the function it appears in. This can cause some headaches.

Try running this (admittedly contrived) example in the console:

for(var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);
  }, 10);
}

The setTimeout() will trigger the supplied function body 10 ms in the future.

Notice how all the values are 10? That’s because we were accessing the same variable i, because it was in the same scope each time!

The keyword let was introduced in ES6 to bring block scope to JavaScript. If we use it instead, we see the behavior we’re more used to:

for(let i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);
  }, 10);
}

Arrays - Lists by Another Name

CONSOLE

You might have noticed we used an array in discussing the for .. in loop, but didn’t talk about it in our data type discussion. This is because in JavaScript, an array is not a primitive data type. Rather, it’s a special kind of object.

This is one of those aspects of JavaScript that breaks strongly with imperative languages. Brandon Eich drew heavily from Scheme, which is a functional language that focuses heavily on list processing… and the JavaScript array actually has more to do with lists than it does arrays.

Declaring Arrays

JavaScript arrays can be declared using literal syntax:

var arr = [1, "foo", true, 3.2, null];

Notice how we can put any kind of data type into our array? We can also put an array in an array:

var arr2 = [arr, [1,3,4], ["foo","bar"]];

We can create the effect of an n-dimensional array, though in practice we’re creating what we call jagged arrays in computer science.

Clearly if we can do this, the JavaScript array is a very different beast than a Java or C# one.

Accessing Array Values

We can access an element in an array with bracket notation, i.e.:

var arr = [1, "foo", true, 3.2, null];
console.log(arr[3])

will print true. We index arrays starting at 0, just as we are used to .
But what if we try accessing an index that is “out of bounds”? Try it:

var arr = [1,2,3,4];
console.log(arr[80]);

We don’t get an exception, just an undefined, because that value doesn’t exist yet. Let’s take the same array and give it a value there:

arr[80] = 5;
console.log(arr[80]);

Now we see our value. But what about the values between arr[3] and arr[80]? If we try printing them, we’ll see a value of undefined. But remember how we said an array is a special kind of object? Let’s iterate over its keys and values with a for .. in loop:

for(key in arr) {
  console.log(`The index ${key} has value ${arr[key]}`);
}

Notice how we only print values for indices 0,1,2,3, and 80? The array is really just a special case of the object, using indices as property keys to store values. Everything in the array is effectively stored by reference… which means all the rules we learned about optimizing array algorithms won’t apply here.

Arrays as Special-Purpose Data Structures

You’ve also learned about a lot of specialty data structures in prior courses - stacks, queues, etc. Before you write one in JavaScript though, you may be interested to know that JavaScript arrays can emulate these with their built-in methods.

Stacks We push new elements to the top of the stack, and pop them off. The array methods push() and pop() duplicate this behavior by pushing and popping items from the end of the array.

FIFO queues A first-in-first-out queue can be mimicked with the array methods push() and shift() which push new items to the end of the array and remove the first item, respectively.

Another useful method is unshift() , which adds a new element to the front of the array.

Most data types you’ve learned about in prior courses can be emulated with some combination of JavaScript arrays and objects, including various flavors of trees, priority queues, and tries. Granted, these will not be as performant as their equivalents written in C, but they will serve for most web app needs.

Map Reduce

One of the most powerful patterns JavaScript adopted from list-processing languages is the map and reduce patterns. You may have heard of MapReduce in terms of Big Data - that is exactly these two patterns used in combination. Let’s examine how they are used in JavaScript.

Map

The basic idea of mapping is to process a list one element at a time, returning a new list containing the processed elements. In JavaScript, we implement it with the map() method of the array. It takes a function as an argument, which is invoked on each item in the array, and returns the newly processed array (the old array stays the same).

The function supplied to map() is supplied with three arguments - the item currently iterated, the index of the item in the array, and a reference to the original array. Of course, you don’t have to define your function with the second or third arguments.

Let’s try a simple example:

var squares = [1,2,3,4].map((item) => {return item * item})

This code squares each of the numbers in the array, and sets squares to have as a value the array of newly-created squares.

Notice too how by passing a function into the map function, we create a new scope for each iteration? This is how JavaScript has long dealt with the challenge of functional scope - by using functions!

Reduce

The reduce pattern also operates on a list, but it reduces the list to a single result. In JavaScript, it is implemented with the array’s reduce() method. The method takes two arguments - a reducer function and an initial accumulator value. Each time the reduce function iterates, it performs an operation on the currently iterated item and the accumulator. The accumulator is then passed forward to the next iteration, until it is returned at the end.

The function supplied to reduce has four arguments - the current accumulator value, the current iterated item, the item’s index, and the original array. As with map(), we can leave out the last two arguments if we don’t need to use them.

A common example of reduce() in action is to sum an array:

var sum = [1, 2, 3, 4, 5].reduce((acc, item) => {return acc + item}, 0);

We supply the initial value of the accumulator as identity for addition, 0, and each iteration the current item in the array is added to it. At the end, the final value is returned.

MapReduce

And as we said before, MapReduce is a combination of the two, i.e. we can calculate the sum of squares by combining our two examples:

var sumOfSquares = [1,2,3,4,5].map((item) => {
  return item * item
}).reduce((acc, item) => {
  return acc + item
});

Notice how we invoked the map() function on the original array, and then invoked reduce() on the returned array? This is a syntax known as method chaining , which can make for concise code. We could also have assigned each result to a variable, and then invoked the next method on that variable.

Objects and Classes

CONSOLE

JavaScript is also an object-oriented language, but the way it implements objects is derived from the ideas of the Self programming language, rather than the C++ origins of Java and C#’s object-oriented approaches.

Object Properties

Let’s start with what an object is in JavaScript. It’s basically a collection of properties - key/value pairs, similar to the concept of a Dictionary in other languages. The properties play both the role of fields and methods of the object, as a property can be assigned a primitive value or a function.

We’ve already seen how to create an object with literal syntax, but let’s see another example:

var bob = {
  name: "Bob",
  age: 29,
  mother: {
    name: "Mary",
    age: 53
  }
}

Look at the property mother - it is its own object, nested within bob. Objects can nest as deep as we need them to (or at least, until we run out of memory).

We can then access properties with either dot notation or bracket notation, i.e.:

// dot notation
console.log(bob.name);
console.log(bob.mother.name);
bob.father = {name: "Mark"};

// bracket notation
console.log(bob["name"]);
console.log(bob["mother"]["name"]);
bob["father"] = {name: "Mark"}

Property names should conform to JavaScript variable naming rules (start with a letter, $, or _, be composed of letters, numbers, $, and _, and contain no spaces) though we can use bracket notation to sidestep this:

bob["favorite thing"] = "macaroni";

However, if a property set with bracket notation does not conform to the naming rules, it cannot be accessed with dot notation. Other than that, you’re free to mix and match.

You can also use the value of a variable as a property name:

var field = "key";
var tricky = {
  [field]: 1
}
console.lo(tricky.key);

This is a handy trick when you need to set property names at runtime.

Constructors

A constructor in JavaScript is simply a function that is invoked with the keyword new. Inside the body of the function, we have access to a variable named this, which can have values assigned to it. Here is an example:

function Bear(name) {
  this.name = name;
}
var pooh = new Bear("pooh");

There is nothing that inherently distinguishes a constructor from any other function; we can use the new keyword with any function. However, it only makes sense to do so with functions intended to be used as constructors, and therefore JavaScript programmers have adopted the convention of starting function names intended to be used as constructors with a capital letter, and other functions with a lowercase one.

Object Methods

Methods are simply functions attached to the object as a property, which have access to the this (which refers back to the object) i.e.:

pooh.greet = function() {
  console.log(`My name is ${this.name}`);
}

We can also attach a method to all objects created with a constructor by attaching them to its prototype, i.e.:

Bear.prototype.growl = function() {
  console.log(`Grrr.  My name is ${this.name} and I'll eat you up!`)
}

Now we can invoke pooh.growl() and see the same message. If we create a few new Bear instances:

var smokey = new Bear("Smokey");
var shardik = new Bear("Shardik");

They also has access to the growl() method, but not greet(), because that was declared on the pooh instance, not the prototype.

Of course, it doesn’t seem appropriate for Smokey the Bear to threaten to eat you. Let’s tweak his behavior:

smokey.growl = function() {
  console.log("Only you can prevent forest fires!");
}

Now try invoking:

smokey.growl();
shardik.growl();
pooh.growl();

Pooh and Shardick continue to growl menacingly, but Smokey warns us about the dangers of forest fires. This leads us to the topic of prototypes.

Object Prototypes

JavaScript adopts an approach to inheritance known as prototype-based programming , which works a bit differently than you’re used to.

In JavaScript, each object keeps a reference to its constructor (in fact, you can see this for our bears with pooh.constructor, smokey.constructor, etc.). Each constructor in turn has a prototype property, which is an object with methods and properties attached to it.

When we invoke pooh.growl(), JavaScript first checks to see if the growl property is defined on the Bear instance we know as pooh. If not, then it checks the constructor’s prototype for the same property. If it exists, then it invokes it.

Inheritance in JavaScript takes the form of a prototype chain - as each prototype is an object, each prototype can have its own prototype in turn. Thus, when we invoke a method, the interpreter walks down this chain and invokes the first matching property found.

ECMA Script 2015 Class Syntax

If you find this all confusing, don’t worry, you’re not alone. ECMAScript decided to introduce a new class syntax in the 2015 version (ES6). It will look a lot more familiar:

class Bear {
  constructor(name) {
    this.name = name;
    this.growl = this.growl.bind(this);
  }
  
  growl() {
    console.log(`Grrr! My name is ${this.name} and I'll eat you!`);
  }
}

Here we’ve recreated our Bear class using the new syntax. We can construct a bear the same way, and invoke its growl() method:

var yogi = new Bear("Yogi");
yogi.growl();

Method Binding

Under the hood we’re still using the same prototypical inheritance, which throws a slight wrench in the works. Notice the line:

this.growl = this.growl.bind(this);

in the constructor? This uses the function.prototype.bind method to bind the scope of the growl function to the this object of our class (remember, functions start a new scope, and a new scope means a new this object).

So remember when using ES6 class syntax, you need to bind your methods, or declare them in the constructor itself as arrow functions, i.e.:

class Bear {
  constructor(name) {
    this.name = name;
    this.growl = () => { 
      console.log(`Grrr! My name is ${this.name} and I'll eat you!`);
    }
  }
}

As the arrow function declaration does not open a new scope, the this object doesn’t change, and refers to the bear instance.

Inheritance

Specifying inheritance is also simplified. For example:

class Mammal {
  constructor() {
    this.hasFur = true;
    this.givesMilk = true;
    this.heartChambers = 4;
  }
}

class Bear extends Mammal {
  constructor(name) {
    super();
  }
}

Remember to always invoke the parent constructor with super() as the first thing in your child class constructor.

Attaching Scripts

Much like there are multiple ways to apply CSS to a web app, there are multiple ways to bring JavaScript into one. We can use a <script> tag with a specified src attribute to load a separate document, put our code into the <script> tag directly, or even add code to attributes of an HTML element. Let’s look at each option.

Script Tag with Source

We can add a <script> tag with a src attribute that gives a url pointing to a JavaScript file. This is similar to how we used the <link> element for CSS:

<!DOCTYPE html>
<html>
  <head>
    <title>JS Example</title>
  </head>
  <body>
    <script src="example.js"></script>
  </body>
</html>

A couple of important differences though. First, the <script> element is not a void tag, so we a closing </script>. Also, while traditionally we would also place <script> elements in the <head>, current best practice is to place them as the last children of the <body> element.

Script Tag with Content

The reason the <script> element isn’t a void element is that you can place JavaScript code directly into it - similar to how we used the <style> element for CSS. Typically we would also place this tag at the end of the body:

<!DOCTYPE html>
<html>
  <head>
    <title>JS Example</title>
  </head>
  <body>
    <script>
      console.log(1 + 3);
    </script>
  </body>
</html>

Why at the End of the Body?

The reason for placing <script> tags at the end of the body is twofold. First, JavaScript files have grown increasingly large as web applications have become more sophisticated. And as they are parsed, there is no visible sign of this in the browser - so it can make your website appear to load more slowly when they are encountered in the <head> section. Second, JavaScript is interpreted as it is loaded - so if your code modifies part of the web page, and tries to do so before the webpage is fully loaded, it may fail.

A good trick is to place any code that should not be run until all the web pages’ assets have been downloaded within the body of an event handler tied to the 'load' event, i.e.

window.addEventListener('load', function() {
  
  // Put any JavaScript that should not be
  // run until the page has finished loading 
  // here..
  
});

As an Attribute

A third alternative is to define our JavaScript as an on-event handler directly on an element. For example:

<button onclick="console.log(1+3)">click me</button>

This once-common strategy has fallen out of favor as it does not provide for good separation of concerns, and can be difficult to maintain in large HTML files. Also, only one event handler can be set using this approach; we’ll see an alternative method, Element.addEventListener() in the next section that is more powerful.

However, component-based development approaches like React’s JSX make this approach more sensible, so it has seen some resurgence in interest.

Mix-and-Match

It is important to understand that all JavaScript on a page is interpreted within the same scope, regardless of what file it was loaded from. Thus, you can invoke a function in one file that was defined in a separate file - this is commonly done when incorporating JavaScript libraries like JQuery.

Warning

There is one aspect you need to be aware of though. Before you reference code artifacts like functions and variables, they must have been loaded in the interpreter. If you are using external files, these have to be retrieved by the browser as a separate request, and even though they may be declared in order in your HTML, they may be received out of order, and they will be interpreted in the order they are received

There are a couple of strategies that can help here. First, you can use the window’s load event as we discussed above to avoid triggering any JavaScript execution until all the script files have been loaded. And second, we can combine all of our script files into one single file (a process known as concatenation). This is often done with a build tool that also minifies the resulting code. We’ll explore this strategy later in the course.

The Document Object Model

Now that we’ve reviewed the basic syntax and structure of the JavaScript language, and how to load it into a page, we can turn our attention to what it was created for - to interact with web pages in the browser. This leads us to the Document Object Model (DOM).

The DOM is a tree-like structure that is created by the browser when it parses the HTML page. Then, as CSS rules are interpreted and applied, they are attached to the individual nodes of the tree. Finally, as the page’s JavaScript executes, it may modify the tree structure and node properties. The browser uses this structure and properties as part of its rendering process.

The Document Instance

The DOM is exposed to JavaScript through an instance of the Document class, which is attached to the document property of the window (in the browser, the window is the top-level, a.k.a global scope. Its properties can be accessed with our without referencing the window object, i.e. window.document and document refer to the same object).

This document instance serves as the entry point for working with the DOM.

The Dom Tree

The DOM tree nodes are instances of the Element class, which extends from the Node class, which in turn extends the EventTarget class. This inheritance chain reflects the Separation of Concerns design principle: the EventTarget class provides the functionality for responding to events, the Node class provides for managing and traversing the tree structure, and the Element class maintains the element’s appearance and properties.

The DOM Playground

The panels opposite show a simple web app where we can experiment with the DOM. Specifically, we’re going to use JavaScript code we write in playground.js to interact with the page index.html. The page already has a couple of elements defined in it - a button with id="some-button", and a text input with id="some-input".

Additionally, the JavaScript code in page-console.js hijacks the console.log() method so that instead of printing to the regular console, it injects the output to a div element on the page. This will help make the console more accessible in Codio. Also, it demonstrates just how dynamic a language JavaScript is - we just altered the behavior of a core function!

Selecting Elements on the Page

One of the most important skills in working with the DOM is understanding how to get a reference to an element on the page. There are many approaches, but some of the most common are:

Selecting an Element by its ID

If an element has an id attribute, we can select it with the Document.getElementByID() method. Let’s select our button this way. Add this code to your playground.js file:

var button = document.getElementById("some-button");
console.log(button);

You should see the line [object HTMLButtonElement] - the actual instance of the DOM node representing our button (the class HTMLButtonElement is an extension of Element representing a button).

Selecting a Single Element by CSS Selector

While there are additional selectors for selecting by tag name, class name(s), and other attributes, in practice these have largely been displaced by functions that select elements using a CSS selector.

Document.querySelector() will return the first element matching the CSS selector, i.e.:

var button = document.querySelector('#some-button');

Works exactly like the document.getElementById() example. But we could also do:

var input = document.querySelector('input[type=text]');

Which would grab the first <input> with attribute type=text.

Selecting Multiple Elements by CSS Selector

But what if we wanted to select more than one element at a time? Enter document.querySelectorAll() . It returns a NodeList containing all matching nodes. So the code:

var paras = document.querySelectorAll('p.highlight');

Will populate the variable paras with a NodeList containing all <p> elements on the page with the highlight class.

Warning

While a NodeList is an iterable object that behaves much like an array, it is not an array. Its items can also be directly accessed with bracket notation ([]) or NodeList.item() . It can be iterated over with a for .. of loop, and in newer browsers, NodeList.forEach() . Alternatively, it can be converted into an array with Array.from() .

Element.querySelector() and Element.querySelectorAll()

The query selector methods are also implemented on the element class, with Element.querySelector() and Element.querySelectorAll() . Instead of searching the entire document, these only search their descendants for matching elements.

Events

Once we have a reference to an element, we can add an event listener with EventTarget.addEventListener() . This takes as its first argument, the name of the event to listen for, and as the second, a method to invoke when the event occurs. There are additional optional arguments as well (such as limiting an event listener to firing only once), see the MDN documentation for more details.

For example, if we wanted to log when the user clicks our button, we could use the code:

document.getElementById("some-button").addEventListener('click', function(event) {
  event.preventDefault();
  console.log("Button was clicked!");
});

Notice we are once again using method chaining - we could also assign the element to a var and invoke addEventListener() on the variable. The event we want to listen for is identified by its name - the string 'click'. Finally, our event handler function will be invoked with an event object as its first argument.

Also, note the use of event.preventDefault(). Invoking this method on the event tells it that we are taking care of its responsibilities, so no need to trigger the default action. If we don’t do this, the event will continue to bubble up the DOM, triggering any additional event handlers. For example, if we added a 'click' event to an <a> element and did not invoke event.preventDefault(), when we clicked the <a> tag we would run our custom event handler and then the browser would load the page that the <a> element’s href attribute pointed to.

Common Event Names

The most common events you’ll likely use are

  • "click" triggered when an item is clicked on
  • "input" triggered when an input element receives input
  • "change" triggered when an input’s value changes
  • "load" triggered when the source of a image or other media has finished loading
  • "mouseover" and "mouseout" triggered when the mouse moves over an element or moves off an element
  • "mousedown" and "mouseup" triggered when the mouse button is initially pressed and when it is released (primarily used for drawing and drag-and-drop)
  • "mousemove" triggered when the mouse moves (used primarily for drawing and drag-and-drop)
  • "keydown", "keyup", and "keypressed" triggered when a key is first pushed down, released, and held.

Note that the mouse and key events are only passed to elements when they have focus. If you want to always catch these events, attach them to the window object.

There are many more events - refer to the MDN documentation of the specific element you are interested in to see the full list that applies to that element.

Event Objects

The function used as the event handler is invoked with an object representing the event. In most cases, this is a descendant class of Event that has additional properties specific to the event type. Let’s explore this a bit with our text input. Add this code to your playground.js, reload the page, and type something into the text input:

document.getElementById("some-input").addEventListener("input", function(event) {
  console.log(event.target.value);
});

Here we access the event’s target property, which gives us the target element for the event, the original <input>. The input element has the value property, which corresponds to the value attribute of the HTML that was parsed to create it, and it changes as text is entered into the <input>.

Modifying DOM Element Properties

One of the primary uses of the DOM is to alter properties of element objects in the page. Any changes to the DOM structure and properties are almost immediately applied to the appearance of the web page. Thus, we use this approach to alter the document in various ways.

Attributes

The attributes of an HTML element can be accessed and changed through the DOM, with the methods element.getAttribute() , element.hasAttribute() and element.setAttribute() .

Let’s revisit the button in our playground, and add an event listener to change the input element’s value attribute:

document.getElementById("some-button").addEventListener("click", function(event) {
  document.getElementById("some-input").setAttribute("value", "Hello World!")
});

Notice too that both event handlers we have assigned to the button trigger when you click it. We can add as many event handlers as we like to a project.

Styles

The style property provides access to the element’s inline styles. Thus, we can set style properties on the element:

document.getElementById("some-button").style = "background-color: yellow";

Remember from our discussion of the CSS cascade that inline styles have the highest priority.

Class Names

Alternatively, we can change the CSS classes applied to the element by changing its element.classList property, which is an instance of a DOMTokensList, which exposes the methods:

  • add() which takes one or more string arguments which are class names added to the class list
  • remove() which takes one or more string arguments which are class names removed from the class list
  • toggle() which takes one or more strings as arguments and toggles the class name in the list (i.e. if the class name is there, it is removed, and if not, it is added)

By adding, removing, or toggling class names on an element, we can alter what CSS rules apply to it based on its CSS selector.

Altering the Document Structure

Another common use for the DOM is to add, remove, or relocate elements in the DOM tree. This in turn alters the page that is rendered. For example, let’s add a paragraph element to the page just after the <h1> element:

var p = document.createElement('p');
p.innerHTML = "Now I see you";
document.body.insertBefore(p, document.querySelector('h1').nextSibling);

Let’s walk through this code line-by-line.

  1. Here we use Document.createElement() to create a new element for the DOM. At this point, the element is unattached to the document, which means it will not be rendered.
  2. Now we alter the new <p> tag, adding the words "Now I see you" with the Element.innerHTML property.
  3. Then we attach the new <p> tag to the DOM tree, using Node.insertBefore() method and Node.nextSibling property.

The Node interface provides a host of properties and methods for traversing, adding to, and removing from, the DOM tree. Some of the most commonly used are:

Strict Mode

JavaScript has been around a long time, and a lot of JavaScript code has been written by inexperienced programmers. Browser manufacturers compensated for this by allowing lenient interpretation of JavaScript programs, and by ignoring many errors as they occurred.

While this made poorly-written scripts run, arguably they didn’t run well. In ECMA5, strict mode was introduced to solve the problems of lenient interpretation.

Strict mode according to the Mozilla Developer Network :

  1. Eliminates some JavaScript silent errors by changing them to throw errors.
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that’s not strict mode.
  3. Prohibits some syntax likely to be defined in future versions of ECMAScript.

You can place the interpreter in strict mode by including this line at the start of your JavaScript file:

"use strict";

In interpreters that don’t support strict mode, this expression will be interpreted as a string and do nothing.

Regular Expressions

CONSOLE

The JavaScript String prototype has some very powerful methods, such as String.prototype.includes() which recognizes when a string contains a substring - i.e.:

"foobarwhen".includes("bar")

would evaluate to true. But what if you needed a more general solution? Say, to see if the text matched a phone number pattern like XXX-XXX-XXXX? That’s where Regular Expressions come in.

Regular Expressions are a sequence of characters that define a pattern that can be searched for within a string. Unlike substrings, these patterns can have a lot of flexibility. For example, the phone number pattern above can be expressed as a JavaScript RegExp like this:

/\d{3}-\d{3}-\d{4}/

Let’s break down what each part means. First, the enclosing forward slashes (/) indicate this is a RegExp literal, the same way single or double quotes indicate a string literal. The backslash d (\d) indicates a decimal character, and will match a 0,1,2,3,4,5,6,7,8, or 9. The three in brackets {3} is a quantifier, indicating there should be three of the proceeding character - so three decimal characters. And the dash (-) matches the actual dash character.

Writing Regular Expressions and Scriptular

As you can see, regular expressions make use of a decent number of special characters, and can be tricky to write. One of the greatest tools in your arsenal when dealing with Regular Expressions are web apps such as Scriptular.com or Regex101(click the link to open it in a new tab). It lists characters with special meanings for regular expressions on the right, and provides an interactive editor on the left, where you can try out regular expressions against target text.

You can, of course, do the same thing on the console, but I find that using Scriptular to prototype regular expressions is faster. You can also clone the Scriptular Github repo and use it locally rather than on the internet. A word of warning, however, always test your regular expressions in the context you are going to use them - sometimes something that works in Scriptular doesn’t quite in the field (especially with older browsers and Node versions).

Regular Expressions and Input Validation

So how are regular expressions used in Web Development? One common use is to validate user input - to make sure the user has entered values in the format we want. To see if a user entered string matches the phone format we declared above, for example, we can use the RegExp.prototype.test() method. It returns the boolean true if a match is found, false otherwise:

if(/\d{3}-\d{3}-\d{4}/.test(userEnteredString)) {
  console.log("String was valid");
} else {
  console.log("String was Invalid");
}

But what if we wanted to allow phone numbers in more than one format? Say X-XXX-XXX-XXXX, (XXX)XXX-XXXX, and X(XXX)XXX-XXXX)?

We can do this as well:

/\d?-?\d{3}-\d{3}-\d{4}|\d?\s?\(\d{3}\)\s?\d{3}-\d{4}/

The pipe (|) in the middle of the RexExp acts like an OR; we can match against the pattern before OR the pattern after. The first pattern is almost the same as the one we started with, but with a new leading \d and -. These use the quantifier ?, which indicates 0 or 1 instances of the character.

The second pattern is similar, except we use a backslash s (/s) to match whitespace characters (we could also use the literal space , \s also matches tabs and newlines). And we look for parenthesis, but as parenthesis have special meaning for RegExp syntax, we must escape them with a backslash: (\( and \)).

Regular Expressions and Form Validation

In fact, the use of Regular Expressions to validate user input is such a common use-case that when HTML5 introduced form data validation it included the pattern attribute for HTML5 forms. It instructs the browser to mark a form field as invalid unless input matching the pattern is entered. Thus, the HTML:

<label for="phone">Please enter a phone number
  <input name="phone" pattern="\d{3}-\d{3}-\d{4}" placeholder="xxx-xxx-xxxx">
</label>

Will ensure that only validly formatted phone numbers can be submitted. Also, note that we omitted the leading and trailing forward slashes (/) with the pattern attribute.

However, be aware that older browsers may not have support for HTML5 form data validation (though all modern ones do), and that a savvy user can easily disable HTML5 form validation with the Developer Tools. Thus, you should aways validate on both the client-side (for good user experience) and the server-side (to ensure clean data). We’ll discuss data validation more in our chapter on persisting data on the server.

Constructing RegExp

Besides using literal notation, We can also construct regular expressions from a string of characters:

var regexp = new RegExp("\d{3}-\d{3}-\d{4}")

This is especially useful if we won’t know the pattern until runtime, as we can create our regular expression “on the fly.”

RegExp flags

You can also specify one or more flags when defining a JavaScript Regular Expression, by listing the flag after the literal, i.e. in the RegExp:

/\d{3}-\d{3}-\d{4}/g 

The flag g means global, and will find all matches, not just the first. So if we wanted to find all phone numbers in a body of text, we could do:

/\d{3}-\d{3}-\d{4}/g.match(bodyOfText);

Here the RegExp.prototype.match() function returns an array of phone numbers that matched the pattern and were found in bodyOfText.

The flags defined for JavaScript regular expressions are:

  • g global match - normally RegExp execution stops after the first match.
  • i ignore case - upper and lowercase versions of a letter are treated equivalently
  • m multiline - makes the beginning end operators (^ and $) operate on lines rather than the whole string.
  • s dotAll - allows . to match newlines (normally . is a wildcard matching everything but newline characters)
  • u unicode - treat pattern as a sequence of unicode code points
  • ysticky - matches only from the lastIndex property of the Regular Expression

Capture Groups

We saw above how we can retrieve the strings that matched our regular expression patterns Matching patterns represents only part of the power of regular expressions. One of the most useful features is the ability to capture pieces of the pattern, and expose them to the programmer.

Consider, for example, the common case where we have a comma delimited value (CSV) file where we need to tweak some values. Say perhaps we have one like this:

Name,weight,height
John Doe,150,6'2"
Sara Smith,102,5'8"
"Mark Zed, the Third",250,5'11"
... 100's more values....

which is part of a scientific study where they need the weight in Kg and the height in meters. We could make the changes by hand - but who wants to do that? We could also do the changes by opening the file in Excel, but that would also involve a lot of copy/paste and deletion, opening the door to human error. Or we can tweak the values with JavaScript.

Notice how the Mark Zed entry, because it has a comma in the name, is wrapped in double quotes, while the other names aren’t? This would make using something like String.prototype.split() impossible to use without a ton of additional logic, because it splits on the supplied delimiter (in this case, a comma), and would catch these additional commas. However, because a RegExp matches a pattern, we can account for this issue.

But we want to go one step farther, and capture the weight and height values. We can create a capture group by surrounding part of our pattern with parenthesis, i.e. the RegExp:

/^([\d\s\w]+|"[\d\s\w,]+"),(\d+),(\d+'\d+)"$/gm

Will match each line in the file. Let’s take a look at each part:

/^ ... $/mg the m flag indicates that ^ and $ should mark the start and end of each line. This makes sure we only capture values from the same line as par to a match, and the g flag means we want to find all matching lines in the file.

([\d\s\w]+|"[\d\s\w,]+") is our first capture group, and we see it has two options (separated by |). The square brackets ([]) represent a set of characters, and we’ll match any character(s) listed inside. So [\d\s\w] means any decimal (\d), whitespace (\s), or word (\w) character, and the + means one or more of these. The second option is almost the same as the first, but surrounded by double quotes (") and includes commas (,) in the set of matching characters. This means the first group will always match our name field, even if it has commas.

,(\d+), is pretty simple - we match the comma between name and weight columns, capture the weight value, and then the comma between weight and height.

(\d+'\d+)" is a bit more interesting. We capture the feet value (\d+), the apostrophe (') indicating the unit of feet, and the inches value (\d+). While we match the units for inches ("), it is not part of the capture group.

So our line-by-line captures would be:

Line 0: No match
Line 1: John Doe, 150, 6'2
Line 2: Sara Smith, 102, 5'8
Line 3: "Mark Zed, the Third", 250, 5'11

We can use this knowledge with String.prototype.replace() to reformat the values in our file. The replace() can work as you are probably used to - taking two strings - one to search for and one to use as a replacement. But it can also take a RegExp as a pattern to search for and replace. And the replacement value can also be a function, which receives as its parameters 1) the full match to the pattern, and 2) the capture group values, as subsequent parameters.

Thus, for the Line 1 match, it would be invoked with parameters: ("John Doe,150,6'2\"", "John Doe", "150", "6'2\""). We can use this understanding to write our conversion function:

function convertRow(match, name, weight, height) {
  
   // Convert weight from lbs to Kgs 
   var lbs = parseInt(weight, 10);
   var kgs = lbs / 2.205;
  
   // Convert height from feet and inches to meters 
   var parts = height.split("'");
   var feet = parseInt(parts[0], 10);
   var inches = parseInt(parts[1], 10);
   var totalInches = inches + 12 * feet;
   var meters = totalInches * 1.094;
  
   // Return the new line values:
   return `${name},${kgs},${meters}`;
}

And now we can invoke that function as part of String.prototype.replace() on the body of the file:

var newBody = oldBody.replace(/^([\d\s\w]+|"[\d\s\w,]+"),(\d+),(\d+'\d+)"$/gm, convertRow);

And our newBody variable contains the revised file body (we’ll talk about how to get the file body in the first place, either in the browser or with Node, later on).

JSON

JSON is an acronym for JavaScript Object Notation, a serialization format that was developed in conjunction with ECMAScript 3. It is a standard format, as set by ECMA-404 .

JSON Format

Essentially, it is a format for transmitting JavaScript objects. Consider the JavaScript object literal notation:

var wilma = {
  name: "Wilma Flintstone",
  relationship: "wife"
}
var pebbles = {
  name: "Pebbles Flintstone",
  age: 3,
  relationship: "daughter"
}
var fred = {
  name: "Fred Flintstone",
  job: "Quarry Worker",
  payRate: 8,
  dependents: [wilma, pebbles]
}

If we were to express the same object in JSON:

{
  "name": "Fred Flintstone",
  "job": "Quarry Worker",
  "payRate": 8,
  "dependents": [
    {
      "name": "Wilma Flintstone",
      "relationship": "wife"
    },
    {
      "name": "Pebbles Flintstone",
      "age": 3,
      "relationship": "daughter"
    }
  ]
}

As you probably notice, the two are very similar. Two differences probably stand out: First, references (like wilma and pebbles) are replaced with a JSON representation of their values. And second, all property names (the keys) are expressed as strings, not JavaScript symbols.

A discussion of the full syntax can be found in the MDN Documentation and also at json.org .

The JSON Object

The JavaScript language provides a JSON object with two very useful functions: JSON.stringify() and JSON.parse() . The first converts any JavaScript variable into a JSON string. Similarly, the second method parses a JSON string and returns what it represents.

The JSON object is available in browsers and in Node. Open the console and try converting objects and primitives to JSON strings with JSON.stringify() and back with JSON.parse().

Info

While JSON was developed in conjunction with JavaScript, it has become a popular exchange format for other languages as well. There are parsing libraries for most major programming languages that can convert JSON strings into native objects:

Some (like the Python one) are core language features. Others are open-source projects. There are many more available, just search the web!

JSON Nesting and Circular References

While JSON.parse() will handle almost anything you throw at it. Consider this object:

var guy = {
  name: "Guy",
  age: 25,
  hobbies: ["Reading", "Dancing", "Fly fishing"]
};

It converts just fine - you can see for yourself by pasting this code into the console. But what if we add reference to another object?

var guyMom = {
  name: "Guy's Mom",
  age: 52,
  hobbies: ["Knitting", "Needlework", "International Espionage"]
};
guy.mother = guyMom;

Try running JSON.stringify() on guy now:

JSON.stringify(guy);

Notice it works just fine, with Guy’s mother now serialized as a part of the guy object. But what if we add a reference from guyMother back to her son?

guyMom.son = guy;

And try JSON.stringify() on guy now…

JSON.stringify(guy);

We get a TypeError: Converting circular structure to JSON. The JSON.stringify algorithm cannot handle this sort of circular reference - it wants to serialize guy, and thus needs to serialize guyMom to represent guy.mother, but in doing so it needs to serialize guy again as guyMother.son references it. This is a potentially infinitely recursive process… so the algorithm stops and throws an exception as soon as it detects the existence of a circular reference.

Is there a way around this in practice? Yes - substitute direct references for keys, i.e.:

var people = {guy: guy, guyMom: guyMom}
guy.mother = "guyMom";
guyMom.son = "guy";
var peopleJSON = JSON.stringify(people);

Now when you deserialize people, you can rebuild the references:

var newPeople = JSON.parse(peopleJSON);
newPeople["guy"].mother = newPeople[newPeople["guy"].mother];
newPeople["guyMom"].son = newPeople[newPeople["guyMother"].son];

Given a standardized format, you can write a helper method to automate this kind of approach.

Info

The fact that JSON serializes references into objects makes it possible to create deep clones (copies of an object where the references are also clones) using JSON, i.e.:

function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

If we were to use this method on guy from the above example:

var guyClone = deepClone(guy);

And then alter some aspect of his mother:

var guyClone.mother.hobbies.push("Skydiving");

The original guy’s mother will be unchanged, i.e. it will not include Skydiving in her hobbies.

CONSOLE

AJAX

Asynchronous JavaScript and XML (AJAX) is a term coined by Jesse James Garrett to describe a technique of using the XMLHttpRequest object to request resources directly from JavaScript. As the name implies, this was originally used to request XML content, but the technique can be used with any kind of data.

The XMLHttpRequest

The XMLHttpRequest object is modeled after how the window object makes web requests. You can think of it as a state machine that can be in one of several possible states, defined by both a constant and an unsigned short value:

  • UNSENT or 0 The client has been created, but no request has been made. Analogous to a just-opened browser before you type an address in the address bar.
  • OPENED or 1 The request has been made, but the response has not been received. The browser analogue would be you have just pressed enter after typing the address.
  • HEADERS_RECEIVED or 2 The first part of the response has been processed. We’ll talk about headers in the next chapter.
  • LOADING or 3 The content of the response is being downloaded. In the browser, this would be the stage where the HTML is being received and parsed into the DOM.
  • DONE or 4 The resource is fully loaded. In the DOM, this would be equivalent to the 'load' event.

The XMLHttpRequest ready states The XMLHttpRequest ready states

XMLHttpRequest Properties

The XMLHttpRequest object also has a number of properties that are helpful:

  • readyState - the current state of the property
  • response - the body of the response, an ArrayBuffer, Blob, Document, or DOMString based on the value of the responseType
  • responseType - the mime type of response
  • status - returns an unsigned short with the HTTP response status (or 0 if the response has not been received)
  • statusText - returns a string containing the response string fro the server, i.e. "200 OK"
  • timeout - the number of milliseconds the request can take before being terminated

XMLHttpRequest Events

The XMLHttpRequest object implements the EventTarget interface, just like the Element and Node of the DOM, so we can attach event listeners with addEventListener(). The specific events we can listen for are:

  • abort - fired when the request has been aborted (you can abort a request with the XMLHttpRequest.abort() method)
  • error - fired when the request encountered an error
  • load - fired when the request completes successfully
  • loadend - fired when the request has completed, either because of success or after an abort or error.
  • loadstart - fired when the request has started to load data
  • progress - fired periodically as the request receives data
  • timeout - fired when the progress is expired due to taking too long

Several of these events have properties you can assign a function to directly to capture the event:

  • onerror - corresponds to the error event
  • onload - corresponds to the load event
  • onloadend - corresponds to the loadend event
  • onloadstart - corresponds to the loadstart event
  • onprogress - corresponds to the progress event
  • ontimeout - corresponds to the timeout event

In addition, there is an onreadystatechange property which acts like one of these properties and is fired every time the state of the request changes. In older code, you may see it used instead of the load event, i.e.:

xhr.onreadystatechange(function(){
    if(xhr.readyState === 4 && xhr.status === 200) {
        // Request has finished successfully, do logic
    }
});

Using AJAX

Of course the point of learning about the XMLHttpRequest object is to perform AJAX requests. So let’s turn our attention to that task.

Creating the XMLHttpRequest

The first step in using AJAX is creating the XMLHttpRequest object. To do so, we simply call its constructor, and assign it to a variable:

var xhr = new XMLHttpRequest();

We can create as many of these requests as we want, so if we have multiple requests to make, we’ll usually create a new XMLHttpRequest object for each.

Attaching the Event Listeners

Usually, we’ll want to attach our event listener(s) before doing anything else with the XMLHttpRequest object. The reason is simple - because the request happens asynchronously, it is entirely possible the request will be finished before we add the event listener to listen for the load event. In that case, our listener will never trigger.

At a minimum, you probably want to listen to load events, i.e.:

xhr.addEventListener('load', () => {
    // do something with xhr object
});

But it is also a good idea to listen for the error event as well:

xhr.addEventListener('error', () => {
    // report the error
});

Opening the XMLHttpRequest

Much like when we manually made requests, we first need to open the connection to the server. We do this with the XMLHttpRequest.open() method:

xhr.open('GET', 'https://imgs.xkcd.com/comics/blogofractal.png');

The first argument is the HTTP request method to use, and the second is the URL to open.

There are also three optional parameters that can be used to follow - a boolean determining if the request should be made asynchronously (default true) and a user and password for HTTP authentication. Since AJAX requests are normally made asynchronously, and HTTP authentication has largely been displaced by more secure authentication approaches, these are rarely used.

Setting Headers

After the XMLHttpRequest has been opened, but before it is sent, you can use XMLHttpRequest.setRequestHeader() to set any request headers you need. For example, we might set an Accept header to image/png to indicate we would like image data as our response:

xhr.setRequestHeader('Accept', 'image/png');

Sending the XMLHttpRequest

Finally, the XMLHttpRequest.send() method will send the request asynchronously (unless the async parameter in XMLHttpRequest.open() was set to false). As the response is received (or fails) the appropriate event handlers will be triggered. To finish our example:

xhr.send();
Info

A second major benefit of the JQuery library (after simplifying DOM querying and manipulation) was its effort to simplify AJAX. It provides a robust wrapper around the XMLHttpRequest object with the jQuery.ajax() method. Consider the AJAX request we defined in this chapter:

var xhr = new XMLHttpRequest();
xhr.addEventListener('load', () => {
    // do something with xhr object
});
xhr.addEventListener('error', () => {
    // report the error
});
xhr.open('GET', 'https://imgs.xkcd.com/comics/blogofractal.png');
xhr.setRequestHeader('Accept', 'image/png');
xhr.send();

The equivalent jQuery code would be:

jQuery.ajax("https://imgs.xkcd.com/comics/blogofractal.png", {
    method: 'GET',
    headers: {
        Accept: 'image/png'
    },
    success: (data, status, xhr) => {
        // do something with data
    },
    error: (xhr, status, error) => { 
        // report the error 
    }
});

Many developers found this all-in-one approach simpler than working directly with XMLHttpRequest objects. The W3C adopted some of these ideas into the Fetch API.