Skip to content

JavaScript - Data & the DOM

SAP Implementation Consulting & ERP - Startupistan Germany · Block I · study notes for revision.


Up to now my variables held one thing at a time - a name, a price, a yes/no. This chapter is about holding many things and structured things: lists (arrays), described things (objects), and blueprints for making objects (classes). Then it puts them on a real web page through the DOM. The through-line of the whole week: content as data + one repeatable rule beats hand-copying anything.

An array is an ordered list of values stored under one name. I write it with square brackets (an array literal) and read items back by their index - a position number that starts at 0, not 1.

'use strict';
const flavors = ["rye", "spelt", "wheat"];
console.log(flavors[0]); // rye - first item
console.log(flavors[2]); // wheat - last of three
console.log(flavors.length); // 3 - how many items
flavors[1] = "sourdough"; // overwrite the second item
rye
wheat
3

The single most important distinction: some methods change the array in place (mutate it), others leave it alone and return a new value. Getting these mixed up is where surprises come from.

MethodDoes whatMutates?
push() / pop()Add / remove at the endYes
unshift() / shift()Add / remove at the frontYes
includes() / indexOf()Is it present? / at which index?No
slice(start, end)Copy a piece into a new array (end excluded)No
concat() / join(sep)Join arrays / glue items into one stringNo
const flavors = ["rye", "spelt", "wheat"];
flavors.push("olive"); // now 4 items, changed in place
console.log(flavors.pop()); // olive - removed & returned
console.log(flavors.includes("rye")); // true
console.log(flavors.indexOf("wheat")); // 2
console.log(flavors.slice(0, 2)); // [ 'rye', 'spelt' ] - a NEW array
console.log(flavors.join(" and ")); // rye and spelt and wheat

A loop runs a block of code repeatedly. This is how I honour DRY (“Don’t Repeat Yourself”) over data instead of writing near-identical lines by hand.

Visits every item directly - no index bookkeeping. Read it as “for each flavor of the flavors”.

const flavors = ["rye", "spelt", "wheat"];
for (const flavor of flavors) {
console.log(flavor);
}
// rye
// spelt
// wheat

Here is the payoff - the same rule applied to every item, no copy-paste:

const artists = ["Pinkfong", "Asake", "Johnny Cash"];
for (const artist of artists) {
console.log(`=== ${artist} ===`);
console.log(`Listen to ${artist} now on every platform`);
}
=== Pinkfong ===
Listen to Pinkfong now on every platform
=== Asake ===
Listen to Asake now on every platform
=== Johnny Cash ===
Listen to Johnny Cash now on every platform

Add a fourth artist to the array and a fourth card appears for free. Fix the sentence once and every card is fixed. That is DRY in action.

These methods take a callback - a function I hand over, which the method runs once per item. The arrow form ((x) => ...) keeps each one to a readable one-liner. Pin this table above the desk:

MethodReturnsUse it to…
forEachundefineddo something with each item (side effect, e.g. print)
mapa new array, same lengthtransform each item into something new
filtera new array, usually shorterkeep only the items that pass a test
reduceone single valueboil the whole array down to one result
findone item (or undefined)get the first item that passes a test
some / everya booleandoes any / do all items pass a test?
const prices = [4.5, 12, 3.2, 8];
prices.forEach((p) => console.log(p)); // prints each; returns nothing
const withTax = prices.map((p) => (p * 1.07).toFixed(2)); // ['4.82','12.84','3.42','8.56']
const cheap = prices.filter((p) => p < 5); // [ 4.5, 3.2 ]
const total = prices.reduce((sum, p) => sum + p, 0); // 27.7 (0 = starting value)
const firstBig = prices.find((p) => p > 10); // 12
const anyBig = prices.some((p) => p > 10); // true
const allBig = prices.every((p) => p > 10); // false

Arrays and objects are reference types. A variable doesn’t hold the list itself - it holds an arrow (a reference) pointing at the one list in memory. So plain assignment copies the arrow, not the list:

const original = ["rye", "spelt"];
const copy = original; // same arrow → same list!
copy.push("wheat");
console.log(original); // [ 'rye', 'spelt', 'wheat' ] ← the "original" changed too

Two names, one shared list - write through either and both see it. The deliberate way to make a genuinely new array is spread syntax - three dots inside a new literal:

const original = ["rye", "spelt"];
const realCopy = [...original]; // brand-new array with the same items
realCopy.push("olive");
console.log(realCopy.length); // 3
console.log(original.length); // 2 ← untouched

An array answers “which items, in what order?”. An object answers “what do I know about this one thing?”. It’s a collection of named values written with braces (an object literal). Each pair is a property: the key on the left, the value on the right (any type - string, number, boolean, even another array or object).

const croissant = {
name: "Butter Croissant",
price: 2.8,
vegetarian: true,
};
console.log(croissant.name); // Butter Croissant - dot notation
const field = "price";
console.log(croissant[field]); // 2.8 - bracket notation, key from a variable
croissant.price = 3.0; // change a property

Working rule: dots when I can, brackets when the key lives in a variable (or contains characters a plain name can’t). Reading a key that doesn’t exist quietly returns undefined - a misspelled key is the usual culprit.

A method is just a property whose value is a function. Inside a method, the keyword this refers to the object the method was called on - that’s how a method reads its own sibling properties.

const croissant = {
name: "Butter Croissant",
price: 2.8,
describe: function () {
return `${this.name} costs ${this.price} euros`;
},
};
console.log(croissant.describe()); // Butter Croissant costs 2.8 euros

Put the two together and you get the shape of most real data: a list of things, each with named details. Every array tool works unchanged; inside the loop each item answers to dot notation.

const artists = [
{ name: "Pinkfong", genre: "Children's music", total: "11:31" },
{ name: "Adriano Celentano", genre: "Italian pop", total: "20:52" },
{ name: "Asake", genre: "Afrobeats", total: "14:08" },
];
for (const artist of artists) {
console.log(`${artist.name}, ${artist.genre}, ${artist.total} of music`);
}
const popActs = artists.filter((a) => a.genre.includes("pop"));
console.log(popActs.length); // 1

An object has no items to loop over - it has properties. Three built-ins turn its contents into arrays, and then every array tool applies:

HelperReturns
Object.keys(obj)array of the keys (strings)
Object.values(obj)array of the values
Object.entries(obj)array of [key, value] pairs
const hours = { saturday: "7-14", sunday: "8-12", monday: "closed" };
for (const [day, time] of Object.entries(hours)) {
console.log(`${day}: ${time}`);
}
// saturday: 7-14
// sunday: 8-12
// monday: closed

There’s also for...in, which visits an object’s keys directly (for (const day in hours)). It works and shows up in other people’s code, but I prefer the Object.keys / entries route so I stay in array-land with one mental model.

Destructuring pulls properties (or array items) into their own variables in one line. Objects match by key name; arrays match by position.

const croissant = { name: "Butter Croissant", price: 2.8, vegetarian: true };
const { name, price } = croissant; // by key name
console.log(`${name}, ${price} euros`); // Butter Croissant, 2.8 euros
const flavors = ["rye", "spelt", "wheat"];
const [first, second] = flavors; // by position
console.log(first, second); // rye spelt

That [day, time] in the entries loop above was array destructuring on each pair. Destructuring is everywhere in modern frameworks - worth reading fluently now. (Watch the case: const { Name } finds nothing and hands back undefined.)

When every artist object carries the same keys and the same describe method, copying that structure by hand gets silly. Object-oriented programming (OOP) solves it: describe the shape once as a class (the blueprint), then manufacture as many instances (concrete objects) as the data needs.

The compact example shows the whole toolkit - class, constructor, new, a method, plus extends and super for inheritance:

'use strict';
class Artist {
constructor(name, genre, total) { // runs once per new instance
this.name = name; // install data onto THIS instance
this.genre = genre;
this.total = total;
}
describe() { // class method - shorthand, no `function`
return `${this.name}, ${this.genre}, ${this.total} of music`;
}
}
class FeaturedArtist extends Artist { // subclass inherits everything from Artist
constructor(name, genre, total, blurb) {
super(name, genre, total); // run the parent constructor FIRST
this.blurb = blurb; // then add my own property
}
describe() { // override - build on the parent version
return `${super.describe()}. Featured: ${this.blurb}`;
}
}
const cash = new Artist("Johnny Cash", "Country", "15:40");
const asake = new FeaturedArtist("Asake", "Afrobeats", "14:08", "Loudest new voice out of Lagos");
console.log(cash.describe());
console.log(asake.describe());
Johnny Cash, Country, 15:40 of music
Asake, Afrobeats, 14:08 of music. Featured: Loudest new voice out of Lagos
class = blueprintnew = build an instanceconstructor = set up data via thisextends = inheritsuper() = call parent

When the browser reads an HTML file it doesn’t keep the text - it builds the DOM (Document Object Model): a live tree of node objects in memory, one per element, nested exactly as the HTML nests. The rendered page is drawn from this tree, so the one sentence everything rests on: change the tree, and the page changes on screen, instantly.

HTML filethe drawing
→
DOM treelive objects in memory
→
Rendered pagewhat the visitor sees
→
JS edits the treepage updates instantly
The HTML is the architect’s drawing; the DOM is the standing building; JavaScript is the renovation crew with the keys.

First, connect the script to the page. This line goes in the <head>; defer tells the browser to run the file only after the whole document is parsed, so my code doesn’t reach for elements that don’t exist yet:

<script src="app.js" defer></script>

document is the tree’s entry point. querySelector(cssSelector) returns the first element matching a CSS selector (same selectors as CSS - .class, #id, tag); querySelectorAll returns all matches as a list I can loop.

TaskHow
Select first matchdocument.querySelector(".tagline")
Select all matchesdocument.querySelectorAll(".card")
Read / change textel.textContent = "New text"
Add / remove a classel.classList.add("highlight") / .remove("highlight")
Change inline styleel.style.color = "crimson"
'use strict';
const heading = document.querySelector("h1");
heading.textContent = "Startupistan Records"; // tree changes → screen changes
heading.classList.add("highlight"); // CSS defines the look; JS decides when

The division of labour: CSS owns appearance, JavaScript decides when an element wears a class. A selector that matches nothing returns null quietly - remember the dot for classes and the hash for ids.

Reading the page is half the power; the other half is manufacturing content that was never in the HTML. Two tools: createElement(tag) builds a new detached element (it exists only in memory), and append() mounts it inside a parent.

const artists = [
{ name: "Pinkfong", genre: "Children's music", total: "11:31" },
{ name: "Asake", genre: "Afrobeats", total: "14:08" },
];
const cardArea = document.querySelector(".cards");
for (const artist of artists) {
const card = document.createElement("article"); // build the card
const title = document.createElement("h3");
title.textContent = artist.name; // fill from the data
const line = document.createElement("p");
line.textContent = `${artist.genre}, ${artist.total} of music`;
card.append(title, line); // assemble the pieces
cardArea.append(card); // mount into the tree
}

Read the loop body as an assembly line: build → fill from data → assemble → mount. Add a third artist to the array, reload, and a third card appears. This data-to-elements pattern is the beating heart of every framework I’ll meet later.

Everything so far runs once at load. addEventListener(eventName, callback) registers a function to run every time a named event happens. Hand the callback over unrun (no parentheses) - writing () runs it once at load and never again.

For forms, two more pieces: an input’s value property holds whatever the visitor typed, and the submit event fires when they send the form. A submitting form navigates away by default, so I call event.preventDefault() on the event object to stop that and handle it in place.

  1. Grab the elements once, up top.

  2. Listen for submit and stop the default navigation with preventDefault().

  3. Read input.value, guard against empty input (an empty string is falsy), then act on it.

'use strict';
const form = document.querySelector(".signup");
const nameInput = document.querySelector("#artist-name");
const list = document.querySelector(".cards");
form.addEventListener("submit", (event) => { // callback handed over UNRUN
event.preventDefault(); // keep the page put
const name = nameInput.value.trim(); // read what was typed
if (name) { // reject an empty submission
const card = document.createElement("article");
card.textContent = name;
list.append(card); // add the new card live
nameInput.value = ""; // clear the field for the next one
}
});
Must-knowOne-line recall
ArrayOrdered list in one variable; index starts at 0, last is length - 1
Mutating vs notpush/pop/shift/unshift change in place; slice/map/filter/spread return new
Loopsfor...of = every item (default); for = need the index; while = condition-driven
DRYContent as data + one loop replaces every hand-copied line
forEach vs mapforEach returns undefined (acts); map returns a new array (transforms)
filter / find / reducekeep many / get first match / boil down to one value
Reference typesAssigning an array/object copies the arrow, not the thing - use spread [...x] / {...x} to copy
Shallow copySpread copies the top level only; nested references stay shared (structuredClone for deep)
ObjectNamed values (key: value); . when key is literal, [var] when key is in a variable
Method & thisMethod = property holding a function; this = the object before the dot (not an arrow)
Keys/values/entriesTurn an object into arrays; loop entries with a [key, value] destructure
Destructuring{ name } = obj by key name; [a, b] = arr by position
Class / newBlueprint + new builds an instance; constructor installs data via this
extends / superextends inherits; super(...) calls parent constructor; super.m() calls parent method
DOMLive tree of node objects; change the tree → page updates instantly
deferRuns the script after the document is parsed, so selections aren’t null
Select / changequerySelector (first) / querySelectorAll (all); textContent, classList, style
Create / appendcreateElement builds detached; append mounts it - forget append = silent nothing
EventsaddEventListener("click", cb) runs cb each time; hand the callback over unrun
FormsRead input.value; event.preventDefault() stops the submit navigating away