Welcome

Welcome to this introduction to modern JavaScript programming. It focuses on just those aspects of JavaScript most useful for developing dynamic web pages with frameworks such as React .

You can run all the snippets of JavaScript code in this tutorial right in your browser! To do so, open your browser's JavaScript console window.

Key combination Platform Browser
Command-Option-J MacOS Chrome
Command-Option-C MacOS Safari
To enable, open Preferences | Advanced and check "Show Develop menu in menu bar"
Command-Option-K MacOS Firefox
Control-Shift-J Windows Chrome; Edge
Control-Shift-I Windows Firefox
For other browsers, do a web search, e.g., "opera open javascript console".

Open your JavaScript window now. Make sure the Console tab is selected, not Elements or Source. Type 2 + 2 into the window and hit enter. You should see 4 -- the output of your first JavaScript code. Keep the console window open as you continue this tutorial.

Expressions

The first kind of JavaScript code to try are arithmetic expressions. Copy the following into the console window and hit return.

2 + 2

The browser should print 4 on the next line when you hit return. You just ran your first JavaScript code!

You should also be able to double-click the code above to run it in your browser.

Try entering more expressions by hand. For addition and subtraction, use + and -. For multiplication and division use * and /. To raise something to a power, use **. Try really big numbers. Try numbers with decimal points.

Spaces around the operators are optional, but expressions are usually easier to read with them.

JavaScript uses the standard rules for order of operations, so

3 + 4 * 2

will multiply 4 by 2 first, then add 3. To add 3 and 4 and then multiply by 2, use parentheses:

(3 + 4) * 2

What happens with this expression?

(3 + 4) 2

Tip: to run a code example, just double-click it. The code and the result should appear in the console window. Try it with the examples above.

Ready for some more?

Function calls

JavaScript has many math functions besides the basic arithmetic operators. For example, to take the square root of a number, use the function called Math.sqrt, like this

Math.sqrt(100)

This is an example of a function call. Most JavaScript code consists of function calls. A function call is an expression with two parts:

  • The name of the function, like Math.sqrt
  • A list of arguments to pass to the function, in parentheses, like (100)

An example of a function in JavaScript that takes two arguments is Math.hypot. Given the lengths of the sides of a right triangle, it returns the length of the hypoteneuse.

Math.hypot(3, 4)

When more than one argument is needed, separate them with commas. What happens if the comma is missing?

Math.hypot(3 4)

Note that an error message definitely means something is wrong, but the actual message may be misleading about what needs to be fixed.

This page has a complete list of the Math functions available in JavaScript. There are also a few mathematical constants, such as Math.PI. To use a constant like Math.PI, we just write its name. We do not add parentheses because it is not a function. For example, to calculate the circumference of a circle with a radius of 30 meters, write

2 * 30 * Math.PI

On to something new!

Variable assignment

There is a general form to save the result of an expression for later use:

const name = expression;

This declaration creates a variable called name and assigns it the value of the expression. To save a calculated circumference in the variable result:

const result = 2 * 30 * Math.PI;

In our code examples, we will end declarations and other statements with a semicolon, though this is optional most of the time in JavaScript.

Now we can use result in other expressions, like this

4 * result

A variable declared with const can't be reassigned. When playing with assignments in the browser console, you can omit the const. Double-clicking an example does that automatically.
Reloading a web page clears all assignments made in the browser console.

Variable names can be one or more characters. They can start with a letter, dollar sign, or underbar, and be followed by more characters, including digits, e.g., abc_12. They can't contain spaces or other characters. This page discusses common conventions for names in JavaScript.

Got it? Then let's move on!

Function definitions

The expression

2 * 30 * Math.PI

calculates the circumference of a circle with a radius of thirty. This specific calculation can be turned into a function to calculate the area of any circle, given the radius, like this:

const circum = (r) => 2 * r * Math.PI;

This sets the variable circum to be a function that takes one argument, r.

circum(30)

When circum(30) is called, r will be 30. The function then calculates and returns the value of the formula 2 * r * Math.PI.

The general form of a function definition is

const function-name = (list-of-variable-names) => expression;

This declares function-name to have the value of a function expression:

(list-of-variable-names) => expression;

A function expression consists of a list of variable names, an arrow (=>), and an expression. The list of variables is called a parameter list. There can zero or more names in the parameter list. If there is more than one, they are separated by commas. When the function is called, it should be given as many arguments as there are parameter variables.

The list of variables is called the parameter list. The expression is called the body of the function. If there are N parameters, we often say the function "takes" N arguments. For example, circum takes one argument.

Here's a function that returns the average of three numbers:

const ave3 = (x, y, z) => (x + y + z) / 3;
ave3(10, 32, 18)

When a function is called, its parameters are temporarily assigned the values of the arguments in the function call, left to right. Then the expression in the function body is evaluated. The value of that expression is returned as the value of the function call.

Function blocks

With more complex functions, it can be useful to save intermediate calculations in variables to avoid recalculation and label important values. For example, Heron's formula for the area of any triangle is

Math.sqrt(s * (s - a) * (s - b) * (s - c))

where a, b, and c are the sides of the string and s is (a + b + c) / 2. To calculuate this as one expression would require repeating the calculation of s four times.

The better approach would be to calculate s once, store it in a variable, then use it. This can be done by using a function block. A function block is a sequence of statements, surrounded by curly braces, and separated by semicolons. Heron's formula could be implemented like this:

const triangleArea = (a, b, c) => {
  const s = (a + b + c) / 2;
  return Math.sqrt(s * (s - a) * (s - b) * (s - c));
};
triangleArea(3, 4, 5)

The general form of a function block is

const function-name = (parameter-list) => {
  statement;
  statement;
  ...
}

The curly braces tell JavaScript that the function body is a block of statements. You will get a syntax error if you use parentheses instead.

JavaScript will execute each statement from top to bottom. By default, a block returns undefined when it completes. Specify a value to return with a return statement. A return statement exits the block immediately, so put it at the end of the block, like this

const function-name = (parameter-list) => {
  statement;
  statement;
  ...
  return expression;
}

Variable scope

In this function

const triangleArea = (a, b, c) => {
  const s = (a + b + c) / 2;
  return Math.sqrt(s * (s - a) * (s - b) * (s - c));
};

the variables a, b, c, and s are all local variables. That means that they are only visible to code inside triangleArea. You can safely define other functions with the same variables. No confusion or conflict will occur.

s> is declared as a constant. That means it can't be changed later in the function. There are ways to define variables that can be reset, but this is rarely needed. Code with local constants is easier to understand and maintain.

Summary

Functions are the heart of programming. They are the bricks from which all big programs are built.

Now to move on!

Composition of expressions

Expressions are simple but powerful. A lot can be done by combining smaller expressions into bigger ones. For example, the Greeks struggled to find a purely geometric way to square the circle, i.e., given a circle, construct a square with exactly the same area. What they wanted to do turns out to be impossible, but we can define a function that takes the radius of a circle and returns the length of the side of a square with the same area:

const squareCircle = (radius) => Math.sqrt(Math.PI * radius ** 2);
squareCircle(10)

The expression in the function body first calculates the area of the circle, using the formula π r2 then passes that area to the square root function to find the side of a square with the same area.

This function definition is a bit long for one line. When a function has a big expression, you can write it more readably by

  • writing a left parenthesis after the =>, to mark the beginning of the functon body,
  • writing the body on a new line, indented two spaces,
  • writing a right parenthesis on a new line, not indented, to mark end of the function body

A good programming editor, like VS Code, will automatically take care of the indentation for you, when you type the parentheses and hit return.

Here is what squareCircle would look like written this way.

const squareCircle = (radius) => (
  Math.sqrt(circleArea(radius))
);

When your brain is ready, take the next step!

Strings

Besides numbers, most applications need text, e.g., names, labels, messages, and so on. In JavaScript, text is represented with the string data type. The simplest way to specify a string of text is by placing the text between a pair of single or double quotation marks, like this

'this is a string'
"this is a string"

Modern JavaScript style guides recommend single quotation marks for most situations.

String construction

To construct a string from data, e.g., to make the string "Hello, name" where name is replaced by the value is in the variable name, use a template string. A template string uses backticks (`) rather than quotation marks, like this

`this is a template string`

Insert data into a template string with ${expression} forms.

`the sum of 2 and 3 is ${2 + 3}.`
const name1 = 'Pat';
`Hello, ${name1}!`
`the circumference of a circle of radius 10 is ${circum(10)}.`

String properties and methods

Many types of data in JavaScript have associated properties. For example, strings have a length property. To get the value of a property for a given piece of data, write the data, then a dot, i.e., period, then the name of the property.

'this is a string'.length
const helloMsg = 'Hello, Pat!';
helloMsg.length

In JavaScript, the value of some data properties is a function. Such a property is called a method. A method can be called to do specific actions with the data. For example, substring is a string method.

helloMsg.substring

returns a function that can be called with two numeric arguments: start and end, like this

helloMsg.substring(0, 5)
helloMsg.substring(7, 10)

The start and end are indices, i.e., positions in the string specified as offsets from the start. Zero is the position of the first character. Since the length of helloMsg is 11, the last character is at position ten. The string method substring returns a string of the characters from start up to, but not including end.

l

To see if a string contains a substring, use search.

helloMsg.search('Pat')
helloMsg.search('Hello')
helloMsg.search('Goodbye')

search returns the index, as defined above, where the argument string is first found. It returns -1 if the search fails. Case matters.

helloMsg.search('pat')

There are many many string methods. Whenever you want to do something with a string, first look to see if there's a method that does it for you. See this page for a complete list.

Numbers. Strings. But wait! There's more!

Objects

JavaScript has a general purpose form, called an object, that can be used to represent complex data. An object is created by writing a list of properties and values inside curly braces. For example, we could define an object representing a panel of word that 4 meters by 7 meters with:

const panel1 = { width: 4, height: 7 };

This declares panel1 to have the object { width: 4, height: 7 }. The width and height property values can be accessed with the dot operator.

panel1.width
panel1.height

We could define a function to get the area of a panel like this

const area = (rect) => rect.width * rect.height;
area(panel1)

A function can create and return an object. For example, the following function would take a radius and return a circle object, with the radius and circumference already calculated:

const makeCircle = (radius) => ({
  radius: radius,
  circumference: 2 * Math.PI * radius
});
makeCircle(5)

The parentheses around the object expression in the body are required. If you leave them out, JavaScript thinks you are trying to write a function block.

A new object can be constructed by merging an existing object with additional values or another object. This is done using the spread operator -- written with three dots (...) in front of the object. Here are three examples

({...panel1, height: 10})
const style1 = { color: 'red', texture: 'rough' };

({...panel1, ...style1})
const addKey = (obj, key, val) => ({
  ...obj, [key]: val
});

addKey(panel1, 'area', area(panel1))

The first example returns a new object with all the properties of panel1 plus the height. The new height replaces the old one in panel1, if any. The second example returns a new object by merging two objects. The third example shows add a property name that is the value of some expression.

Objects give you the power to create your own data structures. You'll use them over and over. But there's more in store!

Functions, methods, and static methods

We will use the phrase function call when calling simple functions like this:

ave3(10, 32, 18)

We will use method call when calling functions attached to data like this:

'Hello, Pat!'.substring(0, 5)

Finally, we will use static method call when calling functions attached to the name of JavaScript class, such as Math or Number, like this:

Math.hypot(3 4)

Just some terms. Ready for a new concept?

Boolean expressions

Boolean expressions are expressions that return true or false. Boolean expressions are necessary to write code that does different things in different situations.

One example of a boolean expression is a "less than" comparison. Here are two examples to try.

25 < 100
125 < 100

JavaScript has numerous operators for comparing numbers. See this page.

These return the JavaScript constants true and false, respectively. JavaScript treats several values as falsy: false, undefined, null, NaN, zero, and the empty string. Every other value is considered truthy.

When we say some expression returns true or false, we almost always mean it returns a truthy or falsy value. It may or may not be returning true or fale specifically.

Use the not operator (!) to negate a boolean value. It returns false if the value is truthy, and true if the value is falsy. Here are some examples. Try others.

! false
! true
! null
! 'a string'

Use equality operator (===) to test if two values are equal. This can be used for numbers and strings. Here are some examples. Try to predict what will happen before running them.

12 === 12
12. === 12
'abc' === 'abc'
'abc' === 'Abc'
12 === '12'

Note that adding a decimal point to a number doesn't matter. Changing case in a string does. For more on equality, see this page.

Do not use the equality operator with just two equal signs (==). It has a complicated definition with unintuitive properties.

Boolean expressions are used in conditionals. Time to see how!

Conditional expressions

A common need in programming is doing different things in different situations. For example, suppose we have some objects representing bottles of wine like this:

const wine1 = { type: 'red', vintage: 1972 };
const wine2 = { type: 'red', vintage: 1980 };
const wine3 = { type: 'white', vintage: 1972 };

We want to define a function selectOlder that takes two wine objects and returns the older one.

const selectOlder = (wine1, wine2) => (
  ...
);

To fill in the body of the function, we need an expression that can return different answers, depending on some test. The form that does that is called a conditional expression. A conditional expression has a test expression that returns true or false, a true branch, which is the expression to return if the test is true, and a false branch, which is the expression to return if the test is false.

The JavaScript syntax for a conditional expression is

(test-expression) ? true-branch : false-branch

In the wine example, the test expression needs to compare the vintages of the two wines. To test if wine1 is older than wine2 we can use the less than (<) operator.

wine1.vintage < wine2.vintage

In the function selectOlder, we want to compare the vintages of the two wines. If the first one is older, we want to return it, otherwise we want to return the second one.

const selectOlder = (wine1, wine2) => (
  (wine1.vintage < wine2.vintage) ? wine1 : wine2
);

What will the following return? Write down your prediction before running the code.

selectOlder(wine1, wine2)
selectOlder(wine2, wine3)
selectOlder(wine1, wine3)

Conditionals are a necessary part of every programming language. Take a breath before moving forward!

Composition of conditionals

Conditional expressions can be composed to test for multiple conditions. For example, let's define a function selectWine that takes two wines. It should return the older one, if possible, or the red one if they are the same age, or the first one. There is more than one way to write this combination of conditionals. Here's one:

const selectWine = (wine1, wine2) => (
  wine1.vintage < wine2.vintage
  ? wine1
  : wine2.vintage < wine1.vintage
  ? wine2
  : wine1.type === 'red'
  ? wine1
  : wine2.type === 'red'
  ? wine2
  : wine1
);

This definition uses a common pattern, called "if-then-else-if". That means if a condition is true, then a value is return. Otherwise the false branch has another condition to check. In selectWine, four conditions are checked. The first one that is true determines the answer.

Try this definition on these examples. Does the output satisfy the requirements for any pairing of wines?

selectWine(wine1, wine2)
selectWine(wine2, wine3)
selectWine(wine1, wine3)

Notice that the code has no test to see if the vintages are equal. Is one needed? Could these tests be done in a different order? Can you think of another way to define selectWine that gets the same answers? Can you think of shorter definition?

You've learned so much! Ready for more?

Arrays

Arrays hold data, like objects do, but arrays are simpler. An array is just a list of values. There are no keys labeling them.

Arrays are created by writing a comma-separated list of values, of any kind, between square brackets, like this:

[1, 2, 3, 4, 5]
['one', 'two', 'three', 'four', 'five']
[{ width: 4, height: 7 }, { width: 2, height: 9 }]

The values are called the elements of the array.

For the next few examples will use the following array of objects, representing wood panels of different sizes:

const panels = [
  { width: 4, height: 7 },
  { width: 2, height: 9 },
  { width: 6, height: 6 }
];

Like strings, arrays have a length property:

panels.length

To get the Nth element of an array, use this special syntax:

array[index]

index should be an integer, 0 or greater, but less than the length of the array. 0 gets the first element, 1 the second, and so on. This is called zero-based indexing. It works the same as with string indexing.

Now try these:

panels[0]
panels[1]
panels[2]

What does this next expression return?

panels[3]

Unlike other programming languages, trying to access an array element that doesn't exist does not cause an error in JavaScript. Instead JavaScript returns the special value, undefined. This is the same value that is returned when we try to retrieve a property from an object that doesn't have that property.

You'll be writing a lot of code that uses arrays, objects, arrays of objects, and objects with array. Ready for what's next?

Array methods

Because lists of data are ubiquitous in programming, JavaScript provides a large number of useful array methods to process lists.

There are a number of methods for seeing if an item is in an array. The simplest to use is includes. Try these two examples.

[3, 5, 1, 8, 12, 8].includes(8)
[3, 5, 1, 8, 12, 8].includes(4)

To find out where in the array an item is, use indexOf or lastIndexOf.

[3, 5, 1, 8, 12, 8].indexOf(8)
[3, 5, 1, 8, 12, 8].lastIndexOf(8)
[3, 5, 1, 8, 12, 8].indexOf(4)

To find an item in a list with a particular property, use find or findIndex. These functions take a function as an argument. They call the function on each element of the array, from left to right. If the function returns true for some element, find returns it, and findIndex returns its index. Using the panels list, try these examples.

panels.find(panel => panel.width < 3)
panels.findIndex(panel => panel.width < 3)
panels.find(panel => panel.width > 7)
panels.findIndex(panel => panel.width > 7)

Array sort

To sort a list of strings, use the array method sort.

['pat', 'andy', 'sandy', 'max'].sort()

Careful! There are several pitfalls with sort. First, it destructively modifies the array. Try this:

const names = ['pat', 'andy', 'sandy', 'max'];
names.sort();
names

The value of names has been changed! To be safe, use the static Array.from method to create a copy of an array, and then sort that copy, like this:

const names1 = ['pat', 'andy', 'sandy', 'max'];
const names2 = Array.from(names1).sort();
names2
names1

Another pitfall is that by default sort compares values as if they were strings. Write down what you think the next piece of code will return. Then try it.

[5, 12, 3, 1, 31].sort()

Surprised? JavaScript treated the numbers like strings. The string '12' alphabetically precedes '3' just as the string 'ab' alphabetically precedes 'c'.

To control how sort works, pass it a comparison function. A comparison function should take two arguments and return a number. The sign of the number returned determines what sort will do. If the function returns

  • a negative number, sort puts the first argument before the second in the result
  • a positive number, sort puts the second argument before the first
  • zero, sort keeps the arguments in input order

Therefore, to sort numbers in ascending order, i.e., small to big, the simplest comparison function just subtracts the second argument from the first:

[5, 12, 3, 1, 31].sort((x, y) => x - y)

To sort numbers in descending order, reverse the subtraction:

[5, 12, 3, 1, 31].sort((x, y) => y - x)

Here are expressions that will sort the panels list by ascending width and by ascending height, respectively. Note the use of Array.from to avoid changing the original array.

Array.from(panels).sort((x, y) => x.width - y.width)
Array.from(panels).sort((x, y) => x.height - y.height)

To sort objects by a string field, use the string method localeCompare. It knows how to sort in many different alphabets.

For example, here's how to sort a list of people objects by name in ascending order:

const people = [
  { name: 'Pat', age: 19, role: 'student' },
  { name: 'Sandy', age: 32, role: 'teacher' },
  { name: 'Max', age: 25, role: 'staff' }
];
Array.from(people).sort((x, y) => x.name.localeCompare(y.name))

Whew! This section has covered just a few of the methods available with arrays. Next we'll look at some array methods for general purpose looping.

Array loops

Arrays have a number of general methods for looping over the elements of the array. The most important are map, filter, and reduce.

Array map

One common processing task is to take a list of data and generate a new list. For example, to get a list of just the names of our list of people, we can use the array method map:

people.map((person) => person.name)

map takes one argument: a function to call. map calls that function with every element in the array, from left to right. map returns a list of the results. Since the function give in this example just returns a person's name, the map call will return a list of names.

Array filter

The array method filter also takes a function and applies that function to every element of the array. But instead of making a list of what the function returns, filter returns a list of those elements of the array for which the function returns a true value. I.e., it filters out those elements for the function returns false.

In JavaScript, false, null, undefined, 0 (zero), NaN ("not a number"), and "" (the empty string) are all false. Anything else is true.

So, to get a list of just the students

people.filter((person) => person.role === 'student')

Array reduce

This method is very general, but takes a bit of practice to learn how to use. Use map, filter, and other array methods when you can. Save reduce for situations where an array has to be "reduced" to a single value, such as a sum or largest value.

Here is how reduce can be used to get the sum of a list of numbers.

const total = (lst) => lst.reduce((sum, n) => sum + n, 0);

The array method reduce takes two arguments: a function, called the reducer, and an initial value. Here the initial value is 0 and the reducer here is

(sum, n) => sum + n

Here's what happens for this example:

total([12, 3, -5, 44, 16])

total calls reduce. reduce calls the reducer on the elements of input array from left to right. For the first call, reduce calles the reducer with the initial value, i.e., 0, and the first element, i.e., 12. So sum is 0, n is 12 and the reducer returns 12.

For the second call, reduce calls the reducer with the value that was just returned, i.e., 12, and the second element, i.e., 3. The reducer therefore return 15.

For the third call, reduce calls the reducer with the 15 just returned and the third element, i.e., -5. The reducer therefore returns 10. This process repeats until the last element is processed. The last value returned by the reducer is the value returned by reduce. In other words, for the input array [12, 3, -5, 44, 16], the reduce returns the value of

12 + 3 + (-5) + 44 + 16

There is a special case. If the array is empty, the initial value is returned immediately, without calling the reducer. Try this expression

total([])

Finding the largest number in an array is another good place to use of reduce.

const max = (lst) => (
  lst.reduce((m, n) => Math.max(m, n), ...)
);

This call the static Math.max method to pick the larger number on each iteration. Math.max takes zero or more arguments and returns the largest.

Math.max(20, -3, 50)

But what should we use for the initial value. -1? That won't work if the input list has negative numbers. Hmmm. What do Math.max do when given zero arguments?

Math.max()

That return value, -Infinity, is a JavaScript constant set to negative infinity. This constant is smaller than any other number. To make this constant easier to see in code, use Number.NEGATIVE_INFINITY.

const max = (lst) => (
  lst.reduce((m, n) => Math.max(m, n), Number.NEGATIVE_INFINITY)
);

Try it out!

max([12, 3, -5, 44, 16])
max([])

Before using reduce, see if there's an array method that already does what you want. A complete list of array methods can be found on this page.

We still have not covered all the array methods available. Bookmark the Array methods page . Browse through it now and refer back to it when you work with arrays.

Let's move on!

Composition by chaining

Chaining is a way to compose expressions involving object properties and methods. A chain is a series of property access or method calls, where each call operates directly on the value returned by the expression before it.

An example of chaining is this expression that copies and sorts an array:

Array.from(lst).sort()

A common use of chaining is retrieving data inside nested objects. For example, given this object representing a billing for someone in the United States:

const order = {
  payee: {
    name: 'Pat Sanders',
    address: {
      street: '1313 Mockingbird Lane',
      city: 'Camelot', 
      state: 'NJ',
      country: 'USA'
    }
  },
  billing: {
    subtotal: 100.33,
    tax: 10.33
  }
};

To get the state where the customer lives, use this chain:

order.payee.address.state

The same thing can be done when calling array methods. For example, given this list of people, this chain returns just the names of students in our array of people, rather than the entire objects:

people.filter((person) => person.role === 'student').map((person) => person.name)

When a chain becomes somewhat long like this, it can be broken up onto separate lines for readability:

people.filter((person) => person.role === 'student')
      .map((person) => person.name)

Would it work to do the map first and then the filter? Not sure? Try it!

Nearly done with this introduction! Onward!

Working with mixed types

This is a section showing common ways to switch between different related types of data. It is not about types in general or TypeScript in particular. Those topics are beyond this introductory material.

Numbers and strings

Because web pages and web forms are text-based,it frequently happens that you get strings when what you want are numbers, or you have numbers when you need strings. A common method to convert a string to a number is to apply unary addition. This forces JavaScript to do type coercion and parse the string as a number. Try it on the following strings.

+ "23"
+ "-45.78"
+ "12ab"
+ "  "

The first two cases show that unary addition works as expected for strings with positive and negative numbers. The last two cases show two potential pitfalls. A string with nonnumeric elements returns NaN, but an empty or blank string returns zero. If your string is coming from user input, you need to decide how you want to handle those cases.

const getNumber = (text) => (
(!text.trim()) ? Number.NaN : + text
);
['23', '-45.78', '  ', '12ab'].map(getNumber)

To convert a number to a string, you can use a template string:

`${12}`
`${-45.78}`

Arrays and strings

Given an array, to get a string with the elements of the array, use the array method join. It takes one argument, which is the string to use to separate the elements.

[1, 2, 3, 4].join(',')
['Max', 'Pat', 'Sandy'].join('; ')

A string can be split into an array of its parts, using the string method split. It takes a string that is used to find where to split the string. If the empty string is given, an array of the characters in the string is returned.

"1,2,3,4".split(',')
'Max; Pat; Sandy'.split('; ')
'a short phrase'.split(' ')
'a short phrase'.split('')

Arrays and objects

Arrays and objects both hold data. Each have their advantages. For example, here are two ways to represent a list of panels of different widths and heights.

const panelsList = [
{ id: 'panel1', width: 4, height: 7 }, 
{ id: 'panel2', width: 2, height: 9 },
{ id: 'panel3' , width: 6, height: 6 }
];
const panelsObject = { 
panel1: { width: 4, height: 7 }, 
panel2: { width: 2, height: 9 },
panel3: { width: 6, height: 6 }
};

panelsList is easier to loop over, e.g., to collect panels that are of a certain size. panelsObject is easier to get data about a particular panel by ID.

If you need to do both things in your code, use the object form. When you need to loop over the objects, use either Object.keys or Object.values, as appropriate.

Object.keys(panelsObject)
Object.values(panelsObject)

If for some reason you have an array, you can create an object with the less often used static Object.fromEntries method. It requires an array of elements, where element is an array containing a key and a value. Such a list could be constructed from panelsList as follows

Object.fromEntries(panelsList.map(panel => [ panel.id, panel ]))

IF statements

Sometimes in a function block with multiple statements, it is useful to have a way to test for some special case at the start and return immediately. This is done with an IF statement. For example, the following code defines within to take three string arguments: a text, a left delimiter, and a right delimiter. It returns everything in the text between the left and right delimiters.

const within = (str, left, right) => {
  const m = str.indexOf(left);
  if (m === -1) return '';
  const n = str.indexOf(right, m + 1);
  if (n === -1) return '';
  return str.slice(m + 1, n).trim();
};
within('Manfred (the Wonder Dog) barked!', '(', ')')
within('Manfred, the Wonder Dog, barked!', ',', ',')

The first IF statement exits if the left delimiter is not found. No search for the right delimiter occurs. The second IF exits if the right delimiter isn't found following the left delimiter. The slice and trim is only called if both m and n have valid values.

There are several forms for IF statements. For more, see this page.

Use IF statements sparingly. They can rapidly lead to code that is hard to read and debug.

Onward!

Dates and timestamps

Often, a program that is collecting data will need a "timestamp" to record when the data was collected. This might be to mark when a social media post was made, when a move was made in a game, or when someone logged into an application.

To get a timestamp, use the static method Date.now. Try calling it several times to see what it returns:

Date.now()

What is Date.now() returning? It is the current date and time — according to your computer settings — represented as the number of milliseconds since January 1, 1970, 00:00:00 UTC. "UTC" is the time zone of the Prime Meridian, what used to be called Greenwich Mean Time.

Using a simple integers for timestamps makes them easy to add and subtract, easy to store in text files and databases, and avoids problems with timezones, leap years, and different calendar systems. Negative timestamps are used to represent dates before 1970. This approach is based on Unix timestamps.

For example, one way to represent a post in a social media app, with the author, text, and time of posting, is this:

const makePost = (author, text) => ({
  timestamp: Date.now(),
  author: author,
  text: text
});
const post1 = makePost('sandy', 'my first post!')

To create a readable string version of a timestamp, construct a JavaScript Date object from that timestamp, and call the date method toLocaleString on the result.

To create a readable string version of a timestamp, convert it to a JavaScript Date object, and then convert the date to a string with the method toLocaleString. Here's an example:

new Date(818111700000).toLocaleString()

The new operator in JavaScript is used to create instances of certain built-in JavaScript classes. It is not discussed further in this tutorial.

new Date() is the same as new Date(Date.now()).

The string generated will be in the timezone of the user's computer, using a standard short format for the user's region. Additional arguments can be given to toLocaleString to choose the region format to use (called a locale) and the format for the parts, e.g., "December" instead of "12". For details, see this page.

To define a function to display a post the browser console:

const logPost = (post) => {
  const date = new Date(post.timestamp).toLocaleString();
  console.log(`${date}: ${post.author}`)
};
logPost(post1)

To create a string with just the date, use toDateLocaleString. To create a string with just the time, use toTimeLocaleString. For even more control over creating date strings, see Intl.DateTimeFormat.

Event handling

Events in JavaScript include a user clicking on a button, selecting an item from a dropdown menu, or entering text in a field. Many events are actually divided into a series of smaller events. For example, when a user clicks on a button, there will be a mouse down event followed by a mouse up event. If the mouse down and up happened on the same HTML element, there will be a click event on that element. We will look at just a few commonly used events.

For a long list of events that can be handled by JavaScript code, see the MDN Event page.

An event listener, also called an event handler, is a function that is called when an particular event occurs. You create an event listener by attaching a function to an HTML element with the method addEventListener.

element.addEventListener('event-type', function)

For example, the following code when executed will add an event handler to the button Click me!.

document.querySelector('#click-me').addEventListener('click', () => {
  alert('I was clicked.')
});

What happens when you click the button if you run the code above twice?

Clicking is important for buttons. For menus and text fields, usually you are most interested when the menu selection or text field content changes. The event type for this is 'change'. It happens when a menu selection changes or a user types text and hits enter or tab. All event listener functions are passed an Event object. What the object contains depends on the type of event. For change events, the event property target contains the object that changed, and the target's value contains the new value.

Below is a simple text field and dropdown menu, using the HTML select element.

The following lines of code will add change event handlers to the input field and the dropdown menu.

document.querySelector('#user-name').addEventListener('change', (evt) => {
  alert(evt.target.value)
});
document.querySelector('#user-house').addEventListener('change', (evt) => {
    alert(evt.target.value)
});

Working with the DOM

DOM is short for Document Object Model. When a browser displays a web page, it creates a DOM object to hold all the data about the elements on the page -- every div, p, img, ul, etc.

The browser makes the DOM available to JavaScript in the variable document. This represents the entire web page, both the head where the title and meta-tags are, and the body, where the user-visible content is.

You can type document into the console window and inspect it. Different browsers will display it different ways.

document is an instance of a Node. Nodes are what the DOM is made out of. A node contains text and other nodes. The HTML elements in the document, like div and img, are instances of HTMLElement, which is a subtype of Node.

Overviews of the DOM, with the type hiearchy and links to all the different types, their properties and methods, can be found in The HTML DOM API, and Introduction to the DOM. This section will just introduce some of the most useful properties and methods, using examples applied to the document you are reading.

Finding nodes and collecting text

One task often done with the DOM is scraping it for data. For example, the table of contents on these pages is collected dynamically by JavaScript. It works by getting all the h2 and h3 elements that have an id attribute.

Here's code to get the h2 elements that have an id.

document.querySelectorAll('h2[id]')

The element method querySelectorAll() takes a CSS selector as its one argument. When applied to an element, such as document, it returns a list of all the elements contained by that element that match the selector. With one method, you have all the power of CSS for finding HTML elements!

Every HTML element has an innerText property, but the following does not work to get the header texts.

document.querySelectorAll('h2[id]').map(h => h.innerText)

It says map is not defined because querySelectorAll returns a NodeList. That is not a JavaScript array. Fortunately, Array.from() can make an array from a NodeList, so this works:

Array.from(document.querySelectorAll('h2[id]')).map(h => h.innerText)

A related method is querySelector(). It takes a CSS selector like querySelectorAll() but returns only the first element that matches. It's particularly useful when looking for a node with a specific ID.

document.querySelector('#change-text').innerText

Finding a node and changing it

It turns out innerText is a settable property. Try running this code.

document.querySelector('#change-text').innerText = 'Found and changed!';

There's an even more powerful property you can set: innerHTML. This lets you change the HTML in an element. Try this:

document.querySelector('#change-text').innerHTML =
  '<span style="color:red">Found and changed!</span>';

Try the same code but using innerText.

Being able to insert HTML means that we can do things like generate a table of contents for a page by collecting headers and building the HTML for list. Surprisingly little code is needed for this. Just three key steps: collect strings, make HTML, and store HTML.

const items = Array.from(document.querySelectorAll('h2[id]')).map(h => h.innerText);
const html = items.map(item => `<li>${item}</li>`).join('');
document.querySelector('#demo-toc').innerHTML = html;
  • future
  • table
  • of
  • contents

A document is an Element, but not an HTML element, so you can't get or set its HTML. You can however set the HTML of document.body.

Changing styles

It is also possible to change how an HTML element appears. This can be done by changing the style of an HTML element or its CSS class list.

A common action is to hide or show some element. This tutorial has JavaScript that shows just one section at a time. You can hide an element by setting its style.display to none. This removes an element from what's displayed, changing how the page is laid out. Alternatively, you can set style.visibility to hidden. This makes an element invisible, but still present on the page, so the page layout is not changed.

A B C
document.querySelector('#big-b').style.display = 'none';
document.querySelector('#big-b').style.display = 'inline';
document.querySelector('#big-b').style.visibility = 'hidden';
document.querySelector('#big-b').style.visibility = 'visible';

When showing an object with display, you have to specify a layout option, e.g., inline or block.

The other way to change how an element looks is to add or remove a CSS class to the element's classList. The classList object has three methods: add, remove, and toggle. Though the first two are fairly easy to understand, code that adds and removes classes can often "get out of synch". Using toggle can lead to shorter more robust code. In particular, use this form

element.toggle(class, expression)

which puts a CSS class on element if and only if expression returns true. This can be used to manage the "active" status of multiple elements in one short loop. For example, this code:

document.querySelector('#digits').addEventListener('input', (evt) => {
  Array.from(document.querySelectorAll('#digit span')).forEach(elt => {
    elt.classList.toggle('on', evt.target.value.includes(elt.innerText))
  })
});

adds a handler to this input field

that will highlight the following digits

0 1 2 3 4 5 6 7 8 9

by toggling this CSS class on the digits:

.on { background-color: yellow; }

Going up the DOM

querySelector and querySelectorAll are good for finding a nested element, but sometimes, after you find it, you want to go "up" the DOM to the element that contains an element you've found.

For example, this document is organized into sections. Each section is a div with the CSS class ac. Each section begins with an h2 header with the class ac-header and a unique ID. The ID for the header of this section is dom-operations. This expression

document.querySelector('#dom-operations').closest('.ac')

finds the section for this header, using the element method closest() to search from the header up the DOM hierarchy for the first element that contains the header that has the CSS class ac.

There is no way with CSS selectors to select an element based on a child it contains. This has to be done with JavaScript.

Document title

One simple property of the document object is its title. The title is what appears in the browser tab for the page and what is shown in the browser history. If you want to change the title based on some calculated data, you can set it the title.

The title of the document is readable.

document.title

The title is settable! Look at your browser's tab for this page. Then run the following line of code and look at the tab again.

document.title = `Prices as of ${new Date().toLocaleTimeString()}`;

© 2024 Chris Riesbeck
Template design by Andreas Viklund