JavaScript (Advanced)
The first JavaScript topic made the page react to the user. This one is about JavaScript that holds up: code that runs later, handlers you can add and remove, errors you catch instead of crash on, forms that explain what is wrong, objects and classes that describe your data, the array methods you will use every day, and the three places a browser can keep data between visits.
Key focus points
setTimeout and setInterval
- setTimeout(fn, ms) runs a function once, after a delay in milliseconds: setTimeout(() => console.log('hi'), 2000) logs after two seconds. 1000 ms is one second.
- It does not pause the script. The code after setTimeout runs right away, and the function runs later, when the delay is over and the script is not busy. That is why 'after' can print before 'inside'.
- setInterval(fn, ms) runs the function again and again, every ms milliseconds, until you stop it.
- Both return an id. Keep it in a variable and pass it to clearTimeout(id) or clearInterval(id) to cancel. Always clear an interval when it is no longer needed: a countdown that reached 0, a clock on a page the user left.
- A delay of 0 does not mean now. The function still waits until the current code has finished.
- Anything you pass after the delay is given to the function as arguments: setTimeout(greet, 1000, 'Ana').
- Typical uses: hide a message after a few seconds, a countdown, a clock, asking the server for news every few seconds, waiting for the user to stop typing before searching.
Try it: run an example and watch the order of the messages in the console. The ones with a page have buttons and fields to use.
- 1. before setTimeout
- 2. after setTimeout, and it is not two seconds yet
addEventListener vs onclick
- The onclick attribute in the HTML and the element.onclick property both work, but each allows one handler only. The next assignment silently replaces the one before it.
- element.addEventListener('click', handler) adds a handler without replacing the others, works for every kind of event, and keeps the JavaScript in the JavaScript file.
- To remove a handler you need the same function: keep it in a variable and call element.removeEventListener('click', handler). An arrow function written inline cannot be removed, because you have no name for it.
- { once: true } as a third argument runs the handler one time and removes it for you.
- The handler gets an event object. event.target is the element that was actually clicked. event.currentTarget is the element the listener is on. They differ when the click lands on a child.
- Events bubble: a click on an <li> also fires on its <ul>, its parent, and so on up to document. One listener on the parent can handle clicks on every child, including children added later. This is called event delegation.
- event.stopPropagation() stops the bubbling. event.preventDefault() stops the default action, like a form submitting. They are two different things.
Try it: run an example, then click on the page. The console shows which listeners ran, and in what order.
<button id="a">onclick property</button> <button id="b">addEventListener</button>- Nothing logged yet. Look at the page above, or interact with it.
try, catch, finally
- When a line throws an error and nothing catches it, the script stops at that line. Everything after it never runs, and the user sees a page that quietly does nothing.
- try { ... } catch (error) { ... }: the code in try runs. If a line throws, JavaScript jumps to catch with the error, and the script continues after the block.
- The error object has a name (TypeError, ReferenceError, SyntaxError, RangeError) and a message. Log both while developing. Show the user something they can act on.
- finally runs in every case: after try finished, after catch ran, even when there is a return inside. Use it for cleanup: hide the spinner, enable the button again.
- throw new Error('Age must be a number') creates your own error. A function that cannot do its job should throw, and the code that called it decides what to tell the user.
- Use try/catch for things that can fail even when your code is right: JSON.parse on text you did not write, reading from storage, talking to a server. Do not use it to hide bugs in your own code. Fix those.
- Wrap only the lines that can fail. A try around the whole script catches everything and tells you nothing.
Try it: run each example and compare what reaches the console. Then remove a try block and run again.
- 1. this runs
- Uncaught SyntaxError: Unexpected token 'h', "this is not JSON" is not valid JSON
Improve form validations
- The HTML attributes (required, minlength, type="email", pattern) are the first line. The browser checks them on submit and shows its own message. Keep them: they work with JavaScript off.
- To show your own messages, add novalidate to the <form>, listen for submit, call event.preventDefault() and check each field yourself. The attributes still describe the rules: input.checkValidity() tells you if a field passes them.
- input.validity says what is wrong: valueMissing, tooShort, typeMismatch, patternMismatch. input.validationMessage is the browser's text for it.
- Show the error next to the field, not in an alert: a <small class="error"> under the input, and a class on the input so CSS can color the border.
- Once a field has shown an error, check it again on every input event, so the message disappears the moment the user fixes it.
- A regular expression describes a pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) is a reasonable email check, /^\d{10}$/ is exactly ten digits. test returns true or false.
- Rules that compare two fields, like password and confirmation, cannot be attributes. Check them in the submit handler, and input.setCustomValidity('The passwords do not match') makes the browser treat the field as invalid. Set it back to '' when they match.
- Validation in the browser is for the user, so they get fast feedback. The back-end must check everything again: anyone can send a request without your form.
Try it: submit each form empty, then half filled, then correctly. Each example takes over a little more from the browser.
<form id="f">
<label>Email <input name="email" type="email" required></label>
<label>Password <input name="password" type="password" minlength="8" required></label>
<button>Sign up</button>
</form>
<p id="out"></p>- Nothing logged yet. Look at the page above, or interact with it.
Objects: normal, nested and with classes
- An object literal is the simplest shape: const user = { name: 'Ana', age: 20 }. The first JavaScript topic covered reading, changing and looping over its properties.
- Properties can be objects and arrays themselves: user.address.city, user.orders[0].total. That is a nested object, and it is how real data looks: a user has an address, an order has items.
- Reading a property of something that is undefined throws. user.phone?.number, with optional chaining, gives undefined instead of an error when phone is missing.
- Destructuring pulls properties into variables: const { name, age } = user. It works on function parameters too: function greet({ name }) { ... }.
- Objects are copied by reference: const b = a makes two names for one object. { ...a } copies the top level only, the nested objects are still shared. structuredClone(a) copies everything.
- A class is a template for objects that share the same properties and methods: class Student { constructor(name, grade) { this.name = name; this.grade = grade } describe() { ... } }. new Student('Ana', 9) builds one.
- class Teacher extends Person reuses everything from Person. super(name) in the constructor runs the parent constructor first. A getter (get isPassing() { ... }) looks like a property but computes its value. A static method belongs to the class, not to one object.
- JSON is the text form of an object: JSON.stringify(user) turns it into a string, JSON.parse(text) turns it back. That is how objects travel to the server and into storage. Methods and classes do not survive the trip, only the data.
Try it: run an example, then add a property, a method or a second class and log the result.
- Tirana
- 12
- 2 orders
- {city: 'Tirana', street: 'Rruga e Durrësit 12', zip: '1001'}
- undefined
- undefined
- Uncaught TypeError: Cannot read properties of undefined (reading 'number')
map, filter, find and reduce
- Each of these takes a function and calls it once per item. You describe what to do with one item, the method does the loop.
- map transforms: a new array of the same length, each item replaced by what your function returns. prices.map(p => p * 2), students.map(s => s.name).
- filter keeps: a new array with only the items where your function returned true. students.filter(s => s.grade >= 5).
- find returns the first item that matches, or undefined. findIndex gives its position. some asks if any item matches, every asks if all of them do.
- reduce folds the array into one value. reduce((total, item) => total + item.price, 0): the first argument carries the result from one item to the next, and the 0 is where it starts.
- map, filter and reduce do not change the original array, so they chain: products.filter(p => p.inStock).map(p => p.price).reduce((a, b) => a + b, 0).
- forEach runs a function for each item and returns nothing. Use it for side effects, like adding elements to the page. If you want a new array, that is map.
- sort changes the array in place and compares as text unless you give it a function: numbers.sort((a, b) => a - b). toSorted does the same and leaves the original alone.
Try it: five products go in at the top. Change the three steps and watch what each one lets through, changes or adds up.
const result = products
.filter(p => p.price <= 4)
.map(p => p.name)
.reduce((n, x) => n + 1, 0)products.filter(p => p.price <= 4).map(p => p.name).reduce((n, x) => n + 1, 0)Now in code: each method takes a function and calls it once per item.
- [6, 5, 8]
- ['Ana', 'Ben', 'Cleo']
- ['Ana: 9', 'Ben: 4', 'Cleo: 7']
- [3, 2.5, 4]
- [6, 5, 8]
localStorage, sessionStorage and cookies
- Variables live as long as the page. Reload, and everything is gone. The browser offers three places to keep data between visits, each separate per site.
- localStorage keeps data until you remove it: across tabs, and after the browser is closed. localStorage.setItem('theme', 'dark'), getItem('theme'), removeItem('theme'), clear().
- sessionStorage has the same methods but belongs to one tab and is cleared when that tab closes.
- Both store strings only. Save an object with JSON.stringify and read it back with JSON.parse. getItem returns null for a key that was never set, so check before you parse.
- Cookies are older and small (about 4 KB), and they are sent to the server with every request. That makes them right for a login session and wrong for app data. document.cookie = 'name=value; max-age=3600; path=/' sets one. Reading document.cookie gives you all of them in one string you have to split.
- A cookie with no expiry disappears when the browser closes. max-age=0 deletes it. Cookies the server marks HttpOnly cannot be read from JavaScript at all, which is the point.
- Nothing in the browser is private: the user can open DevTools and read or change all of it. Never store passwords or anything you would not want edited.
- See it yourself: DevTools > Application (Storage in Firefox) lists localStorage, sessionStorage and the cookies for the site.
Try it: Run builds the page again, like a reload, so run an example twice and watch what survives. The storage pane on the right shows what is saved, like the Application tab in DevTools.
<p id="out"></p>- localStorage.visits = 1
- string
localStorage
| visits | 1 |
sessionStorage
cookies
Good habits
- Clear every interval you start. A forgotten setInterval keeps running in the background.
- One named function per handler, so you can remove it, reuse it and test it.
- Catch errors where you can do something about them. Log the error while you build, show the user a message when you ship.
- Validate in the browser for the user, and on the server for everyone.
- Reach for map, filter and reduce when a loop builds a new array or a total. Reach for find and some when you need to stop at the first match.
- Keep the state of a page in localStorage as one JSON value under one key, not a key per field, and wrap the read in try/catch.
Practice
- On your profile page, show a "Saved" message when the contact form is submitted, and hide it again after three seconds with setTimeout.
- Rewrite the contact form validation: novalidate on the form, a message under each field, and messages that disappear as the user fixes the field. Check the email with a regular expression.
- Remember the dark mode choice in localStorage and apply it when the page loads.
- Make a Student class with a name and an array of grades, a getter average and a describe method. Create five students in an array. Use filter to find the passing ones, map to list their names, reduce to compute the class average and find to look one up by name.
- Build a to-do list where each item is an object { id, text, done }. Store the array in localStorage as JSON, render it with map, toggle done through one click listener on the <ul>, and wrap the localStorage read in try/catch so a broken value does not crash the page.
- Add a countdown with setInterval that goes from 10 to 0 and stops itself.
Resources
Next up: Git: keeping every version of your code and sharing it with others.



