Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

addEventListener
A call on an element that registers a listener for one kind of event.
closest
A way to find the nearest parent of an element that matches a rule you give, starting with the element itself.
closure
A function together with the variables from the place it was written, which stay available when it runs later.
default behaviour
What the browser does on its own when an event happens, such as following a link or submitting a form, before any script intervenes.
DOM
The tree of objects a browser builds from the page, which a script can read and change while the page is open.
element
One item in the page tree, such as a heading or a button, which a script can find and change.
event
Something that happens on the page, such as a click, a key press, or a form being submitted, which a script can respond to.
event bubbling
The way an event rises from the element that received it up through each of its parents.
event target
The element that actually received the action, which may be a child of the element whose listener is running.
function
A reusable block of code that can be called with values and can return an answer to whoever called it.
handler
The function you give to a listener, which the browser runs when the event it waits for happens.
listener
A function registered on an element to wait for one kind of event and run each time that event reaches it.
property
A named value stored on an object, usually read and written with a dot, such as the text of an element.
reference
A value that points at an object rather than holding a copy, so a change made through one is visible through the other.
scope
The part of a program where a name can be used, decided by where it was declared.
variable
A name that holds a value so you can use it again; a constant cannot be pointed at a different value later.

JavaScript from the ground up

Arrays and objects

Many values under one name: lists in order, records with named fields, and the two or three methods worth knowing.

Part 5 of 12

Intermediate15 min readBack to Courses

An array is a list in order

const loaves = ["white", "rye", "focaccia"]; builds an array. Items have positions, counted from zero, and loaves[0] is the first. loaves.length counts them, and loaves.push("spelt") adds to the end.

Arrays keep their order, which is the point when order matters — a queue of steps, a list of names as typed. When you reach for one by position, you are using an array the way it is meant to be used.

The position is the distance from the start, which is why the first item is at zero. That is a rule about counting, not about the data: the array does not know what the items mean. When the meaning matters more than the position, an object is the better shape.

Walking, adding, and building a new list

const loaves = ["white", "rye", "focaccia"];

for (const loaf of loaves) {
  console.log(loaf.toUpperCase());
}

loaves.push("spelt");
console.log(loaves.length);

const lengths = loaves.map((loaf) => loaf.length);
console.log(lengths);

map builds a new array

map runs a function on every item and collects the results into a new array of the same length — one line instead of a loop with an empty array and a push. filter keeps the items a function says yes to; find returns the first one that matches.

The original array is never changed by these. That is deliberate: building a new value from an old one is easier to follow than editing a list in place, and it leaves the original available for whatever else needs it.

An object is a record with names

An object groups related values under names: { name: "Rye", minutes: 40 }. Reach for a field with a dot — recipe.name — and set one with an assignment: recipe.minutes = 45.

Use an array when the order is the point and an object when the names are. A list of recipes is an array; one recipe is an object; and an array of objects is what most real data turns out to be.

A field can hold anything: a string, a number, an array, another object. That nesting is what lets one value describe a whole record, and it is why a shallow copy is not always enough. Reading a missing field gives undefined, so a typo in a property name stays silent until the value is used.

A name points at a list, it does not hold it

Arrays and objects are reference values. const first = [1, 2]; const second = first; does not make a second list — it makes a second name for the same one. Push into second and first changes too, because there is only one array and both names lead to it.

A real copy is made deliberately: const second = [...first]; for an array, { ...recipe } for an object. The copy is shallow, so nested objects are still shared — enough for a flat list, and worth knowing before changing something two levels down.

The methods that change, and the methods that answer

Some array methods change the array they are called on: push, pop, splice and reverse. Others answer with a new array and leave the original alone: map, filter, slice and concat. Reading which is which prevents a surprising amount of "why did my other list change?" debugging.

sort belongs to the first group and has a second surprise: without a comparator it sorts as text, so 10 comes before 9. Give it (a, b) => a - b for numbers. And reading a field that does not exist gives undefined rather than an error, so a misspelled property quietly becomes a blank in the output.

A worked map and filter

Take recipes with three objects. recipes.map((recipe) => recipe.name) calls the function once per object and collects the three names into a new array, in the same order. recipes.filter((recipe) => recipe.minutes < 45) keeps the objects where the answer is true, so the result may be shorter than the input.

Chaining them is where the shape becomes clear: recipes.filter(...).map(...) first narrows the list, then turns each survivor into a name. The original array is untouched at every step, so the same list can feed two reports without either disturbing the other.

Three mistakes worth avoiding: const copy = list aliases rather than copies; list.sort() sorts numbers as text unless you pass (a, b) => a - b; and a misspelled property name returns undefined instead of throwing, so the mistake shows up later as a missing value rather than at the typo.

Try these next

  • Copy an array with spread, change the copy, and log both to prove the original is untouched.
  • Sort [10, 9, 100] once without a comparator and once with (a, b) => a - b.
  • Filter a list of recipes by time, then map the survivors to their names.

A list of recipes, and one of their names

Add a fourth recipe with push, then log every recipe’s name — the whole list, and then a new array of just the names built with map.

Live Preview
Console output will appear here...

3 left to do

  • A recipe is added with push
  • The names are collected with map
  • Something is logged

The array holds objects; each has a name field. recipes.map((recipe) => recipe.name) builds the list of names.

The quick ones, and their total time

From the recipe list, collect the names of every recipe that takes less than 45 minutes, then add up the time of just those recipes. Log both the names and the total.

Live Preview
Console output will appear here...

3 left to do

  • The quick recipes are filtered out
  • Their minutes are added up
  • The names and total are logged

recipes.filter((recipe) => recipe.minutes < 45) keeps the quick ones. map turns them into names, and reduce adds their minutes with a starting value of 0.

push changes the array it belongs to, while map and filter hand you a new one. Knowing which of the two a method does saves a surprising amount of debugging.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Which item is loaves[0]?