JavaScript is not like the other languages.
HTML is structure. CSS is style. PHP is server-side logic, the careful assembly of pages before they are sent. These are all, in their different ways, relatively predictable. They do what you tell them, within certain constraints, and when they do not, the reasons are usually findable.
JavaScript is different.
JavaScript is alive.
JavaScript runs in the browser, after the HTML has been received, while the user is on the page. It can change the page without reloading it. It can respond to things the user does: clicks, scrolls, form inputs, mouse movements, keyboard presses, the device being rotated, the window being resized. It can talk to servers, fetching new data without refreshing the page. It can make animations, validate forms in real time, create interactive games, power entire applications that look and behave like desktop software.
JavaScript can do almost anything.
This is not entirely a good thing.
Because JavaScript can do almost anything, JavaScript is also the language in which almost anything can go wrong, in ways that are sometimes subtle, sometimes spectacular, and occasionally, in the early stages of learning, both simultaneously.
The First Encounter: A Button That Does Stuff
Our Adventurer’s introduction to JavaScript was, appropriately enough, a button.
The blog had acquired a habit that many blogs acquire over time: increasingly long posts with multiple sections, the kind of content that benefits from a “back to top” button that lets readers, having scrolled to the bottom of a three-thousand-word piece, return to the top with a click rather than the sustained thumb effort of scrolling back up through everything they have just read.
A back-to-top button is, in web development terms, a beginner’s exercise. A thing that should take twenty minutes. Our Adventurer had watched a tutorial. The tutorial was ten minutes long. It looked very simple.
It was very simple, in the end, but getting to simple was not simple at all.
The button exists in the HTML as a standard button element. The JavaScript part detects when the user has scrolled past a certain distance and shows the button, and when the button is clicked, scrolls back to the top.
Our Adventurer wrote the JavaScript:
const backToTopButton = document.getElementById('back-to-top');window.addEventListener('scroll', function() { if (window.pageYOffset > 300) { backToTopButton.style.display = 'block'; } else { backToTopButton.style.display = 'none'; }});backToTopButton.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' });});
This code uses a concept called the DOM, the Document Object Model, which is the browser’s representation of the HTML page as a tree of objects that JavaScript can interact with. document.getElementById() finds the element with the ID “back-to-top.” window.addEventListener() listens for the scroll event and runs the function inside it every time the window scrolls. window.scrollTo() moves the scroll position to the top of the page.
Our Adventurer added the JavaScript to the theme. Added the button to the HTML. Tested it in the browser.
The button did not appear when scrolling.
The browser console showed an error:
Cannot read properties of null (reading ‘style’)
This error, Our Adventurer would come to know very well, means: you tried to access a property of an element but the element was null, meaning the browser could not find it. The JavaScript was running before the HTML had fully loaded, before the button element existed in the DOM, and so document.getElementById(‘back-to-top’) was returning null.
The fix was to wrap the code in an event listener that waited until the DOM was ready:
document.addEventListener('DOMContentLoaded', function() { // all the code goes here});
Our Adventurer chose this approach. The button appeared when scrolling past three hundred pixels. The button, when clicked, scrolled smoothly to the top of the page.
Our Adventurer had made a thing happen on a web page. Not just displayed something. Not just styled something. Made something happen, interactively, in response to user behaviour.
The feeling was qualitatively different from previous victories. This was not the satisfaction of a box being the right size or text being the right colour. This was the satisfaction of a machine doing a thing, of code responding to a human doing a human thing like clicking, of the web becoming alive rather than static.
The DOM: Learning to See the Tree
Having encountered the DOM in the context of a single button, Our Adventurer decided to understand it properly.
The Document Object Model is a programming interface for web documents. When a browser loads an HTML page, it parses the HTML and builds a tree structure in memory. Every element is a node in the tree. The html element is the root. The head and body are its children. The elements inside the head and body are their children. And so on, all the way down to individual text nodes within paragraphs.
JavaScript can traverse this tree, find nodes, read their properties, change their properties, add nodes, remove nodes. This is how all interactive web behaviour works. The “clicking a button and something happens” that is so central to web applications is, fundamentally, JavaScript finding a DOM node and modifying it or other nodes in response to user input.
Our Adventurer spent several sessions with the browser developer tools simply exploring the DOM of different websites, seeing how the tree was structured, how elements were nested, how classes and IDs were used to make elements findable by JavaScript. The inspector panel in the developer tools shows the DOM tree directly, and you can expand and collapse nodes, see attributes, and even modify the DOM live in the browser to see what happens.
It was another map. Another way of seeing what was already there.
Events: The Language of Interaction
Events are the mechanism by which JavaScript responds to things happening. There are dozens of events: click, dblclick, mouseover, mouseout, keydown, keyup, focus, blur, submit, load, resize, scroll, and many more. For touch devices: touchstart, touchend, touchmove.
Each event can have one or more event listeners attached to it, functions that run when the event occurs. The addEventListener method is the standard way to attach these:
element.addEventListener('eventType', functionToRun);
There is also the question of event propagation, which is one of JavaScript’s more interesting quirks. When you click on an element, the click event does not just fire on that element. It fires on that element, then on its parent, then on the parent’s parent, all the way up to the document root. This is called event bubbling. You can stop this bubbling with event.stopPropagation() if you need the event to stay contained.
Our Adventurer learned about event delegation through a specific practical problem: a list of blog post tags, each of which should be clickable and do something when clicked. The naive approach is to add an event listener to each tag element individually. The elegant approach is to add a single event listener to the container and use the event’s target property to determine which specific element was clicked.
This matters when elements can be added to the page dynamically, after the initial page load, because event listeners attached to specific elements at page load time will not exist for elements added later. An event listener on a parent that uses event delegation will catch clicks on all child elements, including ones added after page load.
The tag list worked beautifully with event delegation. Single event listener on the tag container. When any tag was clicked, the function checked which tag was clicked, updated the URL, and filtered the post list accordingly.
Our Adventurer wrote in the notebook: “Event delegation is one of those things that seems like an overcomplicated solution until you understand what problem it is solving. Then it seems obvious.”
AJAX: The Web Becomes Seamless
AJAX. Asynchronous JavaScript And XML. A term from 2005 that described a technique that changed the web forever, even though the XML part of it became largely irrelevant almost immediately since JSON is much more pleasant to work with.
AJAX is how web pages load new data without reloading the page. The search bar that shows suggestions as you type. The social media feed that loads more posts when you scroll to the bottom. The like button that updates instantly. The shopping cart that shows the count without a page refresh. All of these are AJAX: JavaScript making requests to the server in the background and updating the page with the response.
In WordPress, AJAX has a specific architecture. There is a PHP file called admin-ajax.php that handles AJAX requests. You register a PHP function as the handler for a specific action. JavaScript sends an AJAX request to admin-ajax.php with that action name. WordPress calls the PHP function, which does whatever it needs to do and echoes a response. JavaScript receives the response and updates the page.
Our Adventurer implemented AJAX search for the blog: a search box that showed results as you typed without navigating to a new page. The PHP side queried WordPress posts matching the search term and returned them as JSON. The JavaScript side listened for input events on the search field, sent AJAX requests to the PHP handler, received JSON responses, and built an HTML list of results that appeared below the search box.
It worked on the third attempt, which felt like an achievement given that the first attempt returned an empty response (a missing wp_die() at the end of the AJAX handler function, which is required to terminate the response correctly) and the second attempt returned the entire WordPress admin interface in the response (a nonce verification error handled incorrectly).
The third attempt was correct. Search results appeared as you typed. They disappeared when you cleared the search. Clicking a result navigated to the post.
Genuinely, actually, impressively interactive.
The Great JavaScript Framework Question
At some point in every web developer’s journey with JavaScript, they encounter The Question: should I learn a framework?
JavaScript frameworks are libraries of pre-written code that provide structured ways of building JavaScript applications. The major ones include React (made by Facebook/Meta), Vue (made by an independent developer and now supported by a community), and Angular (made by Google). There are others. Many others. JavaScript has more frameworks than the average forest has trees, and new ones appear with regular periodicity, each one solving the problems of the previous one and introducing several new ones.
Our Adventurer researched this extensively, which meant reading approximately fifteen articles that each had a different opinion and at least three of which seemed primarily designed to generate arguments in the comments.
The consensus, to the extent that consensus exists in web development communities, was something like: learn vanilla JavaScript well first, understand the DOM, events, asynchronous programming, then learn a framework when you have a specific problem that a framework would solve. Do not reach for a framework because you feel you should. Reach for it because you need it.
For the blog, Our Adventurer decided, frameworks were not yet needed. The blog was a content site, mostly static in nature, with some interactive enhancements. It did not need to be a single-page application. The back-to-top button and AJAX search were JavaScript enough for now.
But React had been duly noted. Duly bookmarked. For later.
Gutenberg: WordPress Meets JavaScript in Production
WordPress’s block editor, Gutenberg, is built in React. This is relevant because working seriously with WordPress in the modern era means encountering React, even if you are not building React applications yourself. Understanding why blocks work the way they do, how the editor and the block interact, what is happening when you use the block API, requires at least a passing familiarity with React concepts.
Our Adventurer investigated block development.
WordPress blocks are React components. Each block has an edit function, the React component that renders in the editor, and a save function, which either renders a static HTML version for the front end or returns null for dynamic blocks whose output is rendered server-side by PHP.
The tooling required to build a custom block: Node.js, npm, the @wordpress/scripts package which handles the build process, converting modern JavaScript syntax into something browsers can understand. This was the first time Our Adventurer had encountered Node.js, which is JavaScript running on a server rather than in a browser, and which has become the standard environment for JavaScript development tooling.
Our Adventurer installed Node.js. Ran the scaffolding command that creates a basic block plugin. Looked at the generated code. Found a large amount of React syntax that was unfamiliar but comprehensible in outline: components, state, props, the JSX syntax that looks like HTML inside JavaScript but is not HTML, it is JSX, which gets compiled to JavaScript function calls.
The custom block created was simple: a “Tip Box” block that displayed a callout with a warning, note, or tip icon depending on which type was selected. The edit component showed a text input and type selector in the editor. The save component rendered styled HTML on the front end.
It took three days.
But it worked, and it was a real WordPress block, a thing that could be published to the WordPress.org plugin repository if polished and documented, a thing that could be used by anyone with WordPress.
The adventure was reaching places Our Adventurer had not expected it to reach.
JavaScript’s Asynchronous Nature: Wrestling with Promises
JavaScript, unlike the other languages Our Adventurer had learned, is fundamentally asynchronous. This means it does not always do things in the order you write them. When JavaScript makes a network request, it does not wait for the response before continuing to the next line of code. It sends the request and moves on, and when the response arrives, it runs a callback function to handle it.
This is efficient. It means the browser does not freeze while waiting for a network request. But it is confusing if you expect code to run top-to-bottom in sequence.
The traditional approach was callbacks: functions passed to other functions to be called when the asynchronous operation is done. This works but can lead to “callback hell,” deeply nested functions that are hard to read and reason about.
Modern JavaScript uses Promises, which represent the eventual result of an asynchronous operation. Promises have .then() methods for when they succeed and .catch() methods for when they fail. You can chain .then() calls to sequence asynchronous operations in a readable way.
Even more modern is async/await, syntactic sugar over Promises that lets you write asynchronous code that looks synchronous:
async function fetchPosts() { try { const response = await fetch('/wp-json/wp/v2/posts'); const posts = await response.json(); return posts; } catch (error) { console.error('Failed to fetch posts:', error); }}
Our Adventurer found async/await considerably more readable than Promise chains, and had a moment of mild frustration at having learned the callback and Promise approaches first only to discover that async/await made them largely unnecessary for new code, followed by the acceptance that understanding the older approaches was necessary to understand why async/await exists and what it is doing under the hood.
Nothing in web development is ever entirely wasted. The hard-won understanding of the lower level is always the foundation of the higher level abstraction.
Debugging JavaScript: A Different Kind of Detective Work
Debugging JavaScript is different from debugging PHP because JavaScript runs in the browser and you can watch it run in real time using the browser’s developer tools.
The console tab in developer tools is where JavaScript errors appear, where you can log values with console.log() to see what they are at any point in the code, where you can run JavaScript commands directly to test things interactively.
The debugger, available in the Sources tab of developer tools, lets you set breakpoints in your code. A breakpoint pauses execution at that line, letting you inspect all the current variable values, step through the code line by line, and understand exactly what is happening at any given moment.
Our Adventurer spent a productive afternoon setting breakpoints in the AJAX search code and stepping through it, watching the variables change, seeing the request go out and the response come back, understanding each step of the process. This kind of direct observation, watching the code actually execute, is one of the most educational things a developer can do.
The JavaScript Battle: Verdict
JavaScript was not defeated in the way CSS and PHP had been. CSS had been understood. PHP had been understood. JavaScript was… ongoing. JavaScript was a language that repaid indefinite study, that had depths that months of learning had only begun to reach, that had an ecosystem of tools and frameworks and patterns that was genuinely enormous and genuinely complex.
But the relevant battles had been won. The back-to-top button worked. The AJAX search worked. The custom Tip Box block worked and was being used in new posts. Readers had commented on how useful the callout system was for navigating longer articles.
The blog was better for JavaScript being in it.
Our Adventurer wrote the final JavaScript notes for this chapter: “JavaScript is like learning to drive after you have learned the highway code and the mechanics of the car. The earlier knowledge helps but driving is its own thing. You have to do it to learn it.”
Three languages down. The integration was coming. The full understanding of how WordPress assembled all of these things together into a working system was almost within reach.
But first, there was one more domain to understand. Not a language of the web, exactly, but the language of organisation, of structure, of the relationship between data and display that underpins everything WordPress does.
The database had been seen. The database had not been understood.
That, too, was about to change.
The adventure was approaching its climax.
The keyboard was ready.
Our Adventurer was ready.
Leave a Reply