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

The newest JavaScript

Three additions that remove a page of helper code each: iterator helpers that work a step at a time, set methods that do real set algebra, and the small conveniences around them.

Part 12 of 12

Advanced16 min readBack to Courses

Iterator helpers

An array has .map and .filter, but they build a whole new array before the next question can be asked. An iterator helper works a step at a time: scores.values().map(double).take(2).toArray() doubles the first two scores and stops there — the rest of the input is never visited.

Generators, Map, Set and String all hand out iterators, so the same chain works on every one of them. Nothing runs until a result is asked for: .toArray(), .reduce(), .forEach(), or a for…of loop.

Array methods are eager: each one finishes before the next begins, and each builds a full array. An iterator helper is lazy, which is why a chain with a filter and a take can skip most of the input. The two styles look alike, and the difference is when the work happens.

Set methods are set algebra

A Set has held unique values for years; now it does the real operations: a.intersection(b) for what both hold, a.union(b) for everything, a.difference(b) for what only the first holds, a.symmetricDifference(b) for what exactly one holds, and a.isSubsetOf(b) for the question in its name.

Each of these used to be a filter with a has inside, or a loop with a second set to collect into. The method says the idea instead of the steps, and the browser does it without building intermediates you never look at.

These methods return a new set rather than changing either operand, so a chain of them reads like an expression. They also accept any iterable, not only another set, which is why a plain array can be passed to union without a conversion.

The small conveniences

Object.groupBy(items, fn) folds an array into an object keyed by what the function returns — the same shape as the reduce you may have written, without the accumulator lines. The result's keys are the group names, and the values are arrays of the items that landed in each.

Promise.try(fn) starts a chain with a function that may throw before it returns anything: the error arrives in the same .catch as a rejected promise would, sync or async. Array.fromAsync(source) collects values from an async source without a loop of awaits.

Lazy means nothing has happened yet

An iterator helper chain is a description of work, not the work itself. scores.values().map(double) returns an iterator; nothing is doubled until something asks for a value. .toArray(), .reduce() and for…of are the asking — the terminal operations.

That is what lets .take(2) save the rest of the input: only two values are ever pulled through the chain. An iterator is also consumed once. After it has been walked to the end, asking again gives nothing, so build a fresh chain rather than storing a half-used one.

Sets compare by identity, groupBy by key

A Set uses SameValueZero: primitives are compared by value, so two "Ada" strings are one member, while two separately created objects are always two members even when their fields match. That is the same identity rule === follows, and it is why sets of objects are more limited than they first appear.

Object.groupBy uses the returned value as a property key, and property keys are strings — so a number 40 becomes "40". Map.groupBy keeps the key as it was, numbers included, and is the right choice when the key is not naturally text.

A worked chain, value by value

Take scores holding 42, 17, 88, 5 and 63, and a chain that keeps the values over 20, doubles each survivor, and takes the first two. Nothing happens when the chain is built. .toArray() asks for the first value: the filter pulls 42, keeps it, the map doubles it to 84, and the take passes it on. The second request pulls 17, which the filter drops, then 88, which becomes 176 and fills the take.

The third request is never made, so 5 and 63 are never read from the array. The work stops at the last value the result needs, and an array method chain would have processed all five before the take could discard the rest.

The same work, said shorter

const scores = [42, 17, 88, 5, 63];

const doubled = scores.values().map((n) => n * 2).toArray();

const teamA = new Set(['Ada', 'Grace', 'Alan']);
const teamB = new Set(['Grace', 'Linus', 'Ada']);
const shared = teamA.intersection(teamB);

const grouped = Object.groupBy(scores, (n) => (n >= 40 ? 'pass' : 'fail'));
console.log(doubled, shared, grouped);

Three mistakes: building an iterator chain and never consuming it, so the console stays empty; walking the same iterator twice and wondering why the second answer is empty; and expecting Object.groupBy keys to stay numbers — they become strings, while Map.groupBy preserves the original key.

Try these next

  • Add .take(2) to an iterator chain and confirm the rest of the array is never visited.
  • Group the same numbers with Map.groupBy and compare the key types with Object.groupBy.
  • Store an iterator, consume it with toArray(), then try to consume it again.

Say it in one line

Double every score with an iterator helper, find what the two teams share with a set method, and group the scores into pass and fail with Object.groupBy. Log all three.

Live Preview
Console output will appear here...

3 left to do

  • The scores go through an iterator helper
  • The teams are compared with a set method
  • The scores are grouped by pass and fail

Scores: scores.values().map((n) => n * 2).toArray(). Teams: make Sets and call .intersection(...). Groups: Object.groupBy(scores, (n) => (n >= 40 ? 'pass' : 'fail')).

Take only what you need

Build an iterator chain that keeps the scores over 20, doubles each one, and takes the first three. Then group the original scores by whether they pass 40, keeping the keys as numbers.

Live Preview
Console output will appear here...

3 left to do

  • The chain takes only three values
  • The chain is consumed with toArray
  • The groups keep numeric keys

scores.values().filter((n) => n > 20).map((n) => n * 2).take(3).toArray() — then Map.groupBy(scores, (n) => (n >= 40 ? 1 : 0)).

These arrived in browsers through 2024 — Chrome 122 and Safari 17.4 onwards for the iterator and set methods. The preview runs in your own browser, so an older one will say so in the console: that is the only real cost of using the language itself.

Check what you learned

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

Part 1 of 5

What does an iterator helper avoid that an array method does not?