JavaScript Fundamentals
A Complete 10-Chapter Course
Table of Contents
- What JS Is, Where It Runs, Embedding in HTML, the Console
- Variables, Data Types, var/let/const
- Operators and Control Flow
- Loops
- Functions
- Arrays
- Objects
- The DOM
- Forms and User Input
- Asynchronous JavaScript
What JS Is, Where It Runs, Embedding in HTML, the Console
JavaScript is the programming language that runs inside web browsers, making pages interactive โ reacting to clicks, updating content without reloading, validating a form before it's submitted. Unlike PHP (covered in the previous series), which runs on the server before a page is sent to the visitor, JavaScript typically runs directly in the visitor's own browser, on their machine.
Where JavaScript Runs
This course focuses entirely on browser JavaScript first โ exactly where most beginners start, and where the immediate, visible feedback of seeing a page react makes learning genuinely engaging.
Embedding JavaScript in HTML โ Three Ways
External files are the standard approach in real projects โ they keep HTML and JavaScript separated (mirroring how CSS is usually kept in its own file too), and the same script file can be reused across multiple HTML pages.
<head> runs before the page's HTML content has loaded โ trying to interact with an element that doesn't exist yet (Chapter 8's DOM manipulation) will fail. Placing <script> tags just before the closing </body> tag, or using the defer attribute, ensures the page's content exists first.
console.log() โ Your Most-Used Tool
console.log() prints a value to the browser's developer console โ opened with F12 or right-click โ Inspect โ Console tab. It's the JavaScript equivalent of PHP's echo/var_dump() from the previous course series, and you'll use it constantly while learning and debugging.
Hello, world!
> 2 + 3
5
> typeof "hello"
'string'
The console can also be typed into directly, like a small interactive scratchpad โ typing an expression and pressing Enter immediately shows its result, without needing a full HTML page or script file at all.
console.error("message") highlights output in red, useful for genuine errors. console.warn("message") shows a yellow warning. console.table(someArray) displays array/object data in a readable table โ genuinely handy once Chapter 6's arrays are introduced.
Statements and Semicolons
Each instruction is a "statement," conventionally ended with a semicolon โ similar to PHP. JavaScript can technically infer missing semicolons in many cases ("automatic semicolon insertion"), but this has subtle edge cases that can cause confusing bugs; this course writes semicolons explicitly throughout, which is the safer, more common professional habit.
Comments
Coding Challenges
Create an HTML file with an external script.js file linked just before the closing </body> tag. Inside script.js, use console.log() to print your name, then a simple calculation like 7 * 6, on two separate lines. Open the page and check the result in the browser console.
๐ View solutionIn the browser console directly (no file needed), try console.log(), console.warn(), and console.error() each with a different short message, and note in a comment what visually distinguishes each one in the console's output.
๐ View solutionWrite a script.js file with three console.log() statements, each preceded by a comment explaining what the line below it does (practice writing both single-line comment styles). Include at least one multi-line /* */ comment describing the whole file's purpose at the top.
๐ View solutionChapter 1 Quick Reference
- JavaScript runs in the browser by default (client-side) โ different from PHP, which runs on the server
- <script src="file.js"></script> โ the standard way to include JS; place near the end of <body>, or use defer
- console.log() โ prints a value to the browser's DevTools console; your most-used debugging tool
- console.error() / console.warn() โ visually distinct variants for errors/warnings
- Statements end with a semicolon โ write them explicitly, despite automatic insertion existing
- // and /* */ โ single-line and multi-line comments
- Next chapter: variables and data types โ var, let, const
Variables, Data Types, var/let/const
Chapter 1 used console.log("Hello, JS!") directly on a literal value. Real code almost always needs to store a value first, under a name, so it can be reused and changed โ that's what a variable is. JavaScript has three keywords for declaring one: var, let, and const โ only two of which are worth reaching for in modern code.
Declaring a Variable
let name = "Philip"; creates a variable called name, holding the value "Philip". There's no need to declare what type of value it will hold up front โ JavaScript figures that out automatically from whatever is assigned.
let vs const โ Can It Be Reassigned?
let can be reassigned later; const cannot โ trying to assign a new value to a const variable throws an error immediately. The rule of thumb: default to const unless the value genuinely needs to change later, then switch to let. This makes code easier to reason about, since a const guarantees that value never changes anywhere below its declaration.
var is the original way to declare a variable, predating let/const (added in 2015). It has looser, more error-prone scoping rules โ a var declared inside an if block leaks out into the surrounding function, unlike let/const, which stay neatly contained to the block they're declared in. Modern code has no good reason to use var.
JavaScript's Core Data Types
Unlike languages with separate integer/decimal types, JavaScript has just one number type covering both. null and undefined are subtly different: undefined means a variable was never assigned anything; null means a value was deliberately set to "nothing" on purpose.
Checking a Value's Type
typeof returns a string naming a value's type โ useful while learning, and occasionally in real code when a value's type genuinely isn't known in advance.
Template Literals โ Building Strings with Variables
Backticks (`) instead of quotes create a template literal, allowing ${ } to embed variables (or any expression) directly inside a string โ far more readable than joining strings together with +.
| Keyword | Reassignable? | Use it when... |
|---|---|---|
| const | No | The default choice โ value won't change |
| let | Yes | The value genuinely needs to change later |
| var | Yes | Avoid โ kept only for reading old code |
Coding Challenges
Declare a const city with your city's name, and a let temperature with a number. Log both using a single template literal in the form "It is X degrees in Y".
๐ View solutionDeclare let score = 0. Reassign it three times (e.g. += 10 each time), logging score after each reassignment. Then declare const maxScore = 100 and try reassigning it, observing the error in the console.
๐ View solutionDeclare one variable of each core type (number, string, boolean, null, undefined) and use typeof to log each variable's type alongside its value.
๐ View solutionChapter 2 Quick Reference
- const โ cannot be reassigned; the default choice
- let โ can be reassigned; use only when the value will actually change
- var โ older, loosely-scoped; avoid in modern code
- Core types: number, string, boolean, null, undefined
- typeof value โ returns a string naming that value's type
- null โ deliberately "no value"; undefined โ never assigned a value
- Template literals: `text ${variable} more text` โ backticks, not quotes
- Next chapter: operators and control flow โ if/else, switch, ternary
Operators and Control Flow
Decisions in JavaScript will look immediately familiar coming from PHP โ the syntax is nearly identical. The genuinely important new concept here is truthy and falsy values, and the strong recommendation to use === rather than == almost universally.
Comparison Operators
| Operator | Meaning |
|---|---|
| === | Strict equality โ value AND type must match, no conversion |
| !== | Strict inequality |
| == | Loose equality โ converts types before comparing (avoid) |
| != | Loose inequality (avoid) |
| > / < / >= / <= | Greater/less than, and "or equal" variants |
== performs type coercion with rules that produce some genuinely surprising results (0 == false, "" == 0, null == undefined). This is the JavaScript equivalent of PHP Fundamentals Chapter 2's == vs === guidance, but the JS community treats it as even more of a hard rule โ production JS code overwhelmingly uses ===/!== by default, reaching for == only in rare, deliberate cases.
Truthy and Falsy โ Values Treated as true/false in a Condition
Falsy (treated as false)
false
0
"" (empty string)
null
undefined
NaN
Truthy (everything else)
true
1, -1, 3.14 (any non-zero number)
"hello", "0", " " (any non-empty string!)
[] (an empty array โ still truthy!)
{} (an empty object โ still truthy!)
"0" and [], is truthy, full stop.
if / else if / else
Note else if is always two words in JavaScript โ unlike PHP, there's no single-word elseif equivalent here.
The Ternary Operator
Identical syntax and use case to PHP's ternary from Fundamentals Chapter 3 โ a compact if/else for simple value assignments.
The Logical OR/AND "Default Value" Trick
const displayName = name ?? "Guest"; โ the "nullish coalescing" operator only uses the fallback when the left side is specifically null or undefined, not for other falsy values like 0 or "". This avoids the surprising case where a genuinely valid value like 0 or an empty string gets incorrectly replaced by ||'s fallback. Covered fully in Intermediate Chapter 1.
switch
Behaves identically to PHP's switch (Fundamentals Chapter 3), including the same fall-through behaviour when break is omitted, and JavaScript's switch always compares using strict equality (===) internally โ never the loose comparison rules from ==.
Coding Challenges
Write five console.log() statements testing whether each of these values is truthy or falsy inside an if/else: 0, "", "false" (the string), null, and [] (empty array). Predict each result first, then verify.
๐ View solutionWrite a function describeTemperature(celsius) using if/else if/else that returns "Freezing", "Cold", "Mild", or "Hot" based on reasonable thresholds. Test it with four different values and console.log() each result.
๐ View solutionWrite a function getDiscount(customerType) using switch that returns 0.2 for "vip", 0.1 for "member", and 0 for anything else (default). Then write a function welcomeMessage(name) using the || fallback trick to default to "Guest" if name is falsy, and test both functions with a few different inputs.
๐ View solutionChapter 3 Quick Reference
- === / !== โ strict comparison, use almost always; == / != โ loose, avoid
- Falsy values (only six): false, 0, "", null, undefined, NaN โ everything else is truthy
- "0" and [] are truthy โ a very common surprise; only the exact six falsy values count
- if / else if / else โ else if is always two words in JS
- Ternary: condition ? a : b
- value || fallback โ uses fallback for ANY falsy value; value ?? fallback โ only for null/undefined (Intermediate Ch 1)
- switch โ same fall-through behaviour as PHP; always compares strictly internally
- Next chapter: loops โ for, while, for...of, for...in
Loops
for and while work essentially identically to PHP. for...of and for...in are new โ and easily confused with each other, since their names look so similar despite doing genuinely different things.
for โ Known Repeat Count
Identical structure to PHP Fundamentals Chapter 4 โ initialiser, condition, step. Note let i rather than a plain variable โ declaring the loop counter with let keeps it properly block-scoped to the loop.
while / do...while
Same behaviour as PHP โ checks the condition before each iteration. do...while also exists in JavaScript with identical "runs at least once" semantics.
for...of โ Iterating Over Values (Arrays, Strings)
for...of gives you each value directly, one at a time โ the closest JavaScript equivalent to PHP's foreach ($array as $value) from Fundamentals Chapter 4. It works on arrays, strings (iterating character by character), and several other "iterable" types covered later in the series.
for...in โ Iterating Over Keys/Property Names
for...in gives you each key (property name) โ designed for plain objects, where there's no single "value" to iterate directly the way an array has. Objects are covered fully in Chapter 7; this is enough to use for...in productively now.
for...in on an array technically works, but iterates over the array's indexes (as strings: "0", "1", "2"), not its values โ and can also pick up unexpected extra properties in some situations. The clear rule: arrays โ for...of (or array methods, Chapter 6). Plain objects โ for...in (or Object.keys(), also Chapter 7).
break and continue
Behave identically to PHP โ break exits the loop, continue skips to the next iteration.
| Loop | Best for |
|---|---|
| for | Known/calculable repeat count |
| while / do...while | Unknown count, condition-driven |
| for...of | Iterating array VALUES (or string characters) |
| for...in | Iterating object KEYS/property names |
map(), filter(), and forEach() โ methods that handle many common "loop over an array and do something" tasks more concisely than a manual loop. Both approaches matter: loops are more flexible and universal, array methods are often clearer for simple transformations.
Coding Challenges
Use a for loop to console.log() the 9 times table from 9ร1 to 9ร12, one line per result.
๐ View solutionGiven const fruits = ["apple", "banana", "cherry", "date"], use for...of to log each fruit, and a separate for...in loop on const car = { make: "Toyota", model: "Corolla", year: 2022 } to log each key and its value. Add a comment explaining why for...in would be the wrong choice for the fruits array.
๐ View solutionUse a while loop to find and log the first power of 2 that exceeds 1000 (i.e. keep doubling a starting value of 1 until it's greater than 1000), logging each doubled value along the way.
๐ View solutionChapter 4 Quick Reference
- for (init; condition; step) โ known/calculable repeat count, same as PHP
- while / do...while โ condition-driven, same as PHP
- for...of โ iterates VALUES of an array/string; closest equivalent to PHP's foreach
- for...in โ iterates KEYS of a plain object; do NOT use on arrays
- break / continue โ same behaviour as PHP
- Array methods (map/filter/forEach, Ch 6) often replace manual loops for simple cases
- Next chapter: functions โ declarations, expressions, arrow functions, scope
Functions
PHP Fundamentals Chapter 5 covered one way to define a function. JavaScript has three distinct syntaxes, each with real behavioural differences โ not just three ways to write the same thing. This chapter covers all three, plus scope, which works similarly to PHP but with one important new wrinkle.
Function Declarations
The most familiar style โ looks almost identical to PHP's function syntax (Fundamentals Chapter 5), minus the $ prefix on parameters.
Function Expressions
Here the function itself is treated as a value, assigned to a const variable โ exactly like assigning a number or string. This reflects something genuinely fundamental about JavaScript: functions are values, storable in variables, passable as arguments, returnable from other functions ("first-class functions"). Chapter 6's array methods (map, filter) rely entirely on this.
Arrow Functions
Arrow functions are a more compact syntax, introduced in 2015 alongside let/const. A single-expression arrow function (like square above) automatically returns that expression's value โ no { } braces or explicit return keyword needed, genuinely useful for short, simple functions.
const square = n => { return n * n; }; โ once braces are added for multiple statements, the implicit return disappears, and return must be written explicitly, exactly like a normal function. Forgetting this is a genuinely common beginner mistake.
Default Parameters
Identical idea to PHP's default parameters from Fundamentals Chapter 5 โ a fallback value used when the argument is omitted entirely.
Function vs Arrow Function โ A Genuinely Important Difference: this
Regular functions get their own this, determined by how they're called. Arrow functions deliberately do not have their own this โ they inherit it from their surrounding context instead. This matters significantly once objects (Chapter 7) and classes (Intermediate Chapter 6) are introduced; for now, the practical rule is: prefer regular function syntax for object methods, arrow functions for short standalone helpers and callbacks.
| Style | Has its own this? | Implicit return? | Good for |
|---|---|---|---|
| function name() {} | Yes | No | Object methods, general-purpose functions |
| const f = function() {} | Yes | No | Assigning a function to a variable, conditionally |
| const f = () => {} | No (inherits) | Only if single-expression, no braces | Short callbacks, array methods (Ch 6) |
Scope โ Mostly Like PHP, With One New Wrinkle
A nested function can read variables from the function it's defined inside โ this is new compared to PHP, where functions are never nested this way. The outer function's variables remain inaccessible from completely outside, exactly like PHP โ but a function defined inside another one has visibility into its parent's scope. This "remembering" behaviour is called a closure, covered in full depth in Intermediate Chapter 2; this chapter just introduces that nested functions can see outer variables.
Coding Challenges
Write the same function โ isEven(number), returning true/false โ three different ways: as a function declaration, as a function expression assigned to a const, and as an arrow function. Test all three with the same input and confirm they all produce identical results.
๐ View solutionWrite an arrow function calculateArea that takes width and height (with height defaulting to width, so it also works for squares with one argument) and returns their product using an implicit return. Call it three different ways and console.log() each result.
๐ View solutionWrite a function makeMultiplier(factor) that returns a NEW function โ one that takes a number and multiplies it by factor. Use it to create double (factor 2) and triple (factor 3), then test both. Explain in a comment why the inner function can still access factor after makeMultiplier has already finished running.
๐ View solutionChapter 5 Quick Reference
- function name() {} โ declaration; familiar, has its own this
- const f = function() {} โ expression; function as a value assigned to a variable
- const f = () => {} โ arrow function; no own this, implicit return for single expressions
- Default parameters: function f(x = "default")
- this differs between regular functions (own this) and arrow functions (inherited) โ matters for object methods
- Nested functions see outer variables โ visibility flows inward; this is the basis of closures (Intermediate Ch 2)
- Functions are values โ "first-class functions," the foundation for array methods in Chapter 6
- Next chapter: arrays โ methods like map, filter, forEach, reduce
Arrays
Chapter 5 established that functions are values that can be passed around. This chapter puts that fact to immediate use: JavaScript arrays come with built-in methods that take a function as an argument and run it against every element. Mastering these four methods replaces almost all manual loop-writing for everyday array work.
Creating and Indexing Arrays
An array is declared with const just like any other value โ the array's contents can still change even though the binding itself can't be reassigned, the same rule that applied to objects would apply here too. Square brackets create the array; square brackets with an index read from it.
Adding and Removing Elements
push/pop work at the end of the array and are fast; unshift/shift work at the start and have to renumber every other element, so they're slower on large arrays.
forEach โ Run a Function for Every Element
forEach takes a function and calls it once per element, passing that element in as the argument. It's a direct replacement for a for loop (Chapter 4) when the only goal is "do something with each item" โ it doesn't build a new array or return a useful value.
map โ Transform Every Element Into a New Array
map runs a function against every element and collects the return values into a brand-new array, leaving the original unchanged. Use map whenever the goal is "the same number of items, but transformed" โ converting, scaling, formatting, and so on.
filter โ Keep Only the Elements That Pass a Test
filter runs a function that returns true or false for each element, and keeps only the elements where it returned true. The result is a new, possibly shorter, array โ the original is left alone, same as map.
reduce โ Combine Every Element Into a Single Value
reduce is the least intuitive of the four, and the most powerful. Its function takes two arguments โ the running result so far (accumulator) and the current element โ and returns the new running result. The 0 after the function is the starting value for the accumulator, used before any element has been processed.
reduce, the first array element is used as the starting accumulator instead, and the callback runs one fewer time. For sums starting at 0 this often still works by accident โ but it silently breaks for anything where the first element isn't a safe starting point (string concatenation, building an object, etc.). Always pass the starting value explicitly.
Chaining Methods Together
Because filter and map each return a new array, their results can be chained directly into the next method call. This reads almost like a sentence: "filter for orders over 5, double each one, then sum them up" โ and is the idiomatic JavaScript style for this kind of data processing.
| Method | Returns | Use it when... |
|---|---|---|
| forEach | undefined | You just need to do something with each item (e.g. log it) |
| map | New array, same length | You need a transformed version of every element |
| filter | New array, same or shorter | You need only the elements that match a condition |
| reduce | A single value | You need to combine everything into one result (sum, total, object) |
Coding Challenges
Given const temps = [18, 22, 15, 30, 27], use map to create a new array converting each Celsius value to Fahrenheit (formula: C * 9/5 + 32). console.log() both the original array and the new one, confirming the original is unchanged.
๐ View solutionGiven const words = ["cat", "elephant", "dog", "hippopotamus", "ant"], use filter to create a new array containing only the words with more than 3 letters. console.log() the result.
๐ View solutionGiven const cart = [{ name: "Book", price: 12 }, { name: "Pen", price: 2 }, { name: "Bag", price: 25 }], use reduce to calculate the total price of all items. Then chain filter and reduce together to calculate the total price of only items costing more than 5.
๐ View solutionChapter 6 Quick Reference
- Indexing: arrays start at index 0; array.length gives the count
- push/pop โ add/remove at the end (fast); unshift/shift โ add/remove at the start (slower)
- forEach โ run a function per element, returns nothing
- map โ transform every element, returns a new array of the same length
- filter โ keep elements passing a test, returns a new (possibly shorter) array
- reduce โ combine all elements into a single value; always pass a starting value
- map/filter/reduce never mutate the original array โ they return new data
- Methods can be chained โ filter().map().reduce() reads like a processing pipeline
- Next chapter: objects โ properties, methods, and the dot/bracket access patterns
Objects
Chapter 6 covered arrays โ ordered lists of values. Objects solve a different problem: grouping named pieces of related data together. Chapter 5's counter example already used one briefly; this chapter covers objects properly, including how methods and this actually work.
Creating an Object and Reading Properties
An object is a set of key: value pairs, wrapped in curly braces. Each key (also called a property name) maps to a value, which can be of any type โ string, number, boolean, even another object or array.
Dot Notation vs Bracket Notation
Dot notation (person.name) is shorter and used by default. Bracket notation (person["name"]) is required whenever the property name is stored in a variable, contains spaces, or is computed at runtime โ dot notation cannot do any of that.
person.field always looks for a property literally named field. person[field] looks up whatever string is currently stored in the field variable. Mixing these up is a common source of silent undefined results.
Adding, Updating, and Deleting Properties
Properties can be added or changed after creation simply by assigning to them โ even though person is declared with const, exactly the same rule that allowed array contents to change in Chapter 6. const only locks the variable binding itself, never the object's internal contents.
Methods โ Functions Stored as Properties
A property whose value is a function is called a method. Inside a method, this refers to the object the method was called on โ person in this case โ which is how greet can reach person.name without naming person directly. This is exactly the regular-function behaviour flagged in Chapter 5: methods should use the function keyword, not arrow syntax, specifically so this works.
Nested Objects and Arrays of Objects
Objects and arrays nest freely inside each other. products here is the same array-of-objects shape used in Chapter 6's cart challenge โ it's worth recognising this pattern, since it's extremely common for representing real-world lists of records (rows from a database, items in a cart, entries in a form).
Looping Over an Object's Properties
for...in (introduced in Chapter 4 for arrays, where it's best avoided) is the natural fit for objects โ it loops over each property name, which can then be used with bracket notation to read the matching value. Notice bracket notation is required here, since subject is a variable holding the property name.
Object.keys(scores) returns ["maths", "science", "art"] as a real array โ meaning map/filter/reduce from Chapter 6 can then be used on an object's data. This combination is covered properly in Intermediate Chapter 1.
Coding Challenges
Create an object book with properties title, author, and pages. Log each property using dot notation, then log the same three values again using bracket notation with a variable holding the property name.
๐ View solutionCreate an object car with properties make, model, and a method describe() that uses this to log a sentence combining make and model. Call describe(), then add a new property year to car afterwards and call describe() again, updating it to include the year.
๐ View solutionCreate an object inventory where each key is an item name and each value is a quantity (e.g. apples: 10, bananas: 5). Use a for...in loop to log every item with its quantity, and keep a running total of all quantities combined, logging the total at the end.
๐ View solutionChapter 7 Quick Reference
- Object literal: { key: value, key2: value2 }
- Dot notation (obj.key) โ short, but key must be a literal name
- Bracket notation (obj[expr]) โ required for variable/dynamic/spaced keys
- const objects are still mutable โ properties can be added, changed, or deleted
- Methods are functions stored as properties; use this inside them to reference the object
- Use function, not arrow syntax, for methods โ arrow functions don't get their own this (Ch 5)
- for...in loops over an object's property names
- Next chapter: the DOM โ selecting and manipulating real HTML elements with JavaScript
The DOM
Every chapter so far has run entirely in the console. The DOM (Document Object Model) is the browser's live, in-memory representation of the HTML page โ and JavaScript can read and change it directly, which is how a page updates without a reload. This chapter is the bridge between "writing JavaScript" and "JavaScript that actually does something visible."
Selecting a Single Element
document.querySelector() takes a CSS selector โ the same syntax used in a stylesheet โ and returns the first matching element on the page, or null if nothing matches. #intro selects by ID, .list-item by class, exactly as in CSS.
Selecting Multiple Elements
querySelectorAll() returns every matching element as a NodeList โ array-like enough that forEach from Chapter 6 works directly on it, even though it's technically not a true array.
querySelector when exactly one element is expected (often by ID). Use querySelectorAll whenever there could be several matches (a class shared by many elements) and all of them need handling.
Reading and Changing Text Content
textContent is a property on the element, just like the object properties from Chapter 7 โ reading it returns the current text, and assigning to it updates the page immediately, with no reload required.
Changing Styles and Classes
element.style sets individual CSS properties directly (camelCase instead of CSS's hyphenated names โ backgroundColor, not background-color). classList is almost always the better choice for anything beyond a one-off tweak, since it keeps styling rules in CSS where they belong and JavaScript just toggles which rules apply.
Responding to Events
addEventListener takes an event name ("click", "input", "submit", and many others) and a function to run when that event happens. This is the first genuinely common, practical use for the function-as-a-value behaviour from Chapter 5 โ the function itself is handed over and the browser calls it later, whenever the click actually occurs.
<script> runs before the HTML below it exists yet, querySelector returns null and calling a method on it throws an error. Either place scripts at the end of <body>, or wrap the code in document.addEventListener("DOMContentLoaded", () => { ... }).
Creating New Elements
createElement builds a new element entirely in memory โ it doesn't appear on the page until it's attached somewhere with appendChild (or a similar method). This pattern โ combined with an array of data and a loop โ is how real pages render dynamic lists.
| Task | Method/Property |
|---|---|
| Select one element | document.querySelector(selector) |
| Select all matching elements | document.querySelectorAll(selector) |
| Read/change text | element.textContent |
| Read/change one CSS property | element.style.property |
| Add/remove/toggle a CSS class | element.classList.add/remove/toggle() |
| Run code on an event | element.addEventListener(event, fn) |
| Build a new element | document.createElement(tag) |
Coding Challenges
Assume the page has a
Hello
. Select it with querySelector, log its current textContent, then change its text to "Updated!" and change its color to blue using element.style. ๐ View solutionAssume the page has a button with id toggleBtn and a div with id panel. Add a click event listener to the button that toggles a class called "open" on panel each time it's clicked, using classList.toggle.
๐ View solutionGiven const fruits = ["Apple", "Banana", "Cherry"] and an empty
Chapter 8 Quick Reference
- querySelector(selector) โ first matching element, or null
- querySelectorAll(selector) โ all matching elements, as a NodeList (forEach works on it)
- element.textContent โ read or change an element's text
- element.style.property โ set one CSS property directly (camelCase)
- element.classList.add/remove/toggle() โ preferred way to change appearance via CSS classes
- element.addEventListener(event, fn) โ run a function when something happens (click, input, etc.)
- document.createElement(tag) + appendChild() โ build and insert new elements dynamically
- Run scripts after the HTML exists โ end of body, or inside a DOMContentLoaded listener
- Next chapter: forms and user input โ reading values, validating, and responding to submission
Forms and User Input
Chapter 8 covered reaching into the page and reacting to clicks. The most common real-world use of that is forms โ text fields, checkboxes, dropdowns โ where a page needs to read what the user typed, check it, and respond. This chapter ties together querySelector, addEventListener, and the conditional logic from Chapter 3 into one practical workflow.
Reading an Input's Value
Unlike most elements, a form input's current text lives in its value property โ not textContent. value is always a string, even for a number input, which matters when doing arithmetic with it.
Listening for Input Changes
The "input" event fires every time the field's value changes โ every keystroke, paste, or autofill โ making it the right choice for live feedback like character counters or instant validation messages, as opposed to "change", which only fires once the field loses focus.
Handling Form Submission
A form's default behaviour on submit is to reload the page and send its data to a server โ almost never what's wanted when JavaScript is handling things instead. event.preventDefault() stops that default behaviour, while still letting the rest of the handler run normally.
Validating Input Before Accepting It
.trim() removes leading/trailing whitespace, so a field containing only spaces is correctly treated as empty. Using return inside the handler stops execution immediately when validation fails, the same early-exit pattern functions have used since Chapter 5 โ nothing after it runs until the user fixes the problem and resubmits.
Checkboxes and Select Dropdowns
Checkboxes use checked (a boolean) rather than value for their state. Dropdowns (<select>) use value, same as text inputs, returning whichever <option>'s value attribute is currently selected.
| Element | Read state with | Useful event |
|---|---|---|
| Text input | input.value | "input" (live) or "change" (on blur) |
| Checkbox | checkbox.checked | "change" |
| Select dropdown | select.value | "change" |
| Whole form | โ | "submit" (always call event.preventDefault()) |
Coding Challenges
Assume an input with id ageInput and a p with id ageOutput. Add an "input" event listener to ageInput that updates ageOutput's textContent live to say "You typed: X" every time the value changes.
๐ View solutionAssume a form with id loginForm containing an input #username and a p#errorBox. On submit, prevent the default reload, trim the username, and if it's empty show "Username is required." in errorBox; otherwise clear errorBox and log a welcome message.
๐ View solutionAssume a checkbox #agreeCheckbox and a button #submitBtn that starts disabled. Add a "change" listener to the checkbox that enables submitBtn (button.disabled = false) when checked, and disables it again (button.disabled = true) when unchecked.
๐ View solutionChapter 9 Quick Reference
- input.value โ current text in an input/textarea/select (always a string)
- checkbox.checked โ boolean state of a checkbox, not value
- "input" โ fires on every keystroke; "change" โ fires once focus leaves the field
- form.addEventListener("submit", ...) โ always call event.preventDefault() first
- .trim() โ strips whitespace before checking if a value is "empty"
- return inside a handler โ early-exit pattern for stopping on invalid input
- element.disabled = true/false โ enable/disable form controls dynamically
- Next chapter: asynchronous JavaScript โ fetch, promises, and async/await
Asynchronous JavaScript
Every example so far has run top to bottom, instantly. Real pages constantly wait on things that take time โ loading data from a server being the most common. JavaScript handles this with promises, and the modern, readable way to work with them: async/await. This is the final Fundamentals chapter, and it pulls together functions (Ch 5), objects (Ch 7), and the DOM (Ch 8โ9) into one realistic workflow.
The Problem: JavaScript Doesn't Wait
setTimeout schedules a function to run later without pausing anything else. JavaScript moves straight on to the next line rather than waiting โ this is what "asynchronous" means: code that will run eventually, not necessarily in the order it's written.
Promises โ A Placeholder for a Future Value
A Promise represents a value that doesn't exist yet, but will at some point โ either successfully (resolve) or with an error (reject). .then() registers a function to run once the promise resolves, receiving the resolved value as its argument.
async/await โ Promises Without the .then() Chains
async before a function lets await be used inside it. await pauses that function (not the whole page) until the promise resolves, then continues with the resolved value โ reading top-to-bottom almost exactly like the synchronous code from every earlier chapter, while still being genuinely non-blocking.
await in a normal function is a syntax error. This is the one new keyword pairing to remember: no async on the function, no await inside it.
fetch โ Getting Real Data from a Server
fetch() requests a URL and returns a promise. The first await gets the response itself; response.json() then parses the body text into a real JavaScript object โ another promise, needing its own await โ at which point Chapter 7's dot/bracket notation works on it normally.
Handling Errors with try/catch
A network request can fail โ no connection, server error, invalid URL. Wrapping await calls in try/catch means a failure jumps straight to the catch block instead of leaving an unhandled error. Without this, a failed request can silently break the rest of the function with no clear feedback to the user.
fetch only rejects (triggering catch) on a genuine network failure โ a 404 or 500 response is still considered a "successful" fetch as far as the promise is concerned. Check response.ok (a boolean) explicitly if a non-success status code needs separate handling.
Putting It Together: Fetch + DOM
This is the complete, realistic pattern: show a loading state immediately, fetch data, then update the page with the result โ or an error message if it fails. Every chapter since Chapter 5 contributes something here: functions, objects, DOM updates, and now asynchronous waiting.
Coding Challenges
Write an async function delayedGreet(name) that uses await with a Promise wrapping setTimeout (1 second) to wait, then console.logs `Hello, ${name}!`. Call it and log "Calling..." immediately before, to confirm the greeting really does log after a delay.
๐ View solutionWrite an async function getPost(id) that fetches from `https://jsonplaceholder.typicode.com/posts/${id}`, parses the JSON, and logs the post's title. Wrap it in try/catch and log "Failed to fetch post." on any error.
๐ View solutionAssume a button #loadBtn and a p#status. Add a click listener that's an async function: on click, set status text to "Loading...", await a fetch to `https://jsonplaceholder.typicode.com/users/1`, then set status to the user's email โ or "Error loading data." if the fetch fails.
๐ View solutionChapter 10 Quick Reference
- Asynchronous code doesn't block โ JavaScript moves on while it's pending
- Promise โ a placeholder for a value that resolves (success) or rejects (failure) later
- async function โ required wrapper to use await inside
- await โ pauses the async function (not the page) until a promise settles
- fetch(url) โ returns a promise for an HTTP response
- response.json() โ parses the response body into a usable object/array
- try/catch โ wraps await calls to handle network/parsing failures gracefully
- response.ok โ check explicitly for non-200 status codes; fetch won't throw on its own for those
- This completes JavaScript Fundamentals. Intermediate begins with closures, the spread/rest operators, and ES6 classes.