The event loop
JavaScript doesn’t run top-to-bottom-and-done the way a PHP request does. Code reacts to events and can run long after the page first loads — this reframes almost everything else.
src\content\docs\left-field\javascript-vs-php.mdx on this? (Following on from “Can you say what would be helpful tips/considerations for people learning JavaScript who are coming from a PHP background?”) If you already know PHP, you’re not starting from zero with JavaScript — variables, functions, loops, and classes all exist in both. But a few of the concepts underneath those familiar keywords are genuinely different, not just renamed. This is a tour of the places where PHP intuition helps, and the places where it’ll quietly mislead you.
This is the one worth internalizing before anything else, because it changes how you think about every other feature.
A PHP script runs on the server, produces a response, and then it’s done — the process ends. JavaScript in the browser runs continuously in an event loop for as long as the page stays open, reacting to clicks, timers, and network responses whenever they happen. There’s no “the script is over now.”
Practically, that also means:
$_GET, $_POST, or $_SESSION. Client-side JS reads form data from the DOM directly and talks to a server explicitly, with fetch.PHP code is mostly synchronous, top to bottom — you call a function, you get a result. JavaScript leans on asynchronous operations constantly: fetch, timers, anything I/O-bound in Node. Under the hood this evolved through callbacks → Promises → async/await.
Start with async/await — it reads closest to the PHP habit of “call it and get the result back”:
async function loadUser(id) { const response = await fetch(`/api/users/${id}`); const user = await response.json(); return user;}The await keyword pauses that function until the Promise resolves, without blocking the rest of the page. That’s the concept to hold onto — nothing else is running while a PHP script waits on a database, but plenty else can be running while JS awaits a fetch.
$ sigil — variables are just let total = 0;.== performs type coercion in both languages, but the coercion rules aren’t the same — don’t trust PHP instincts here. Default to === / !== in JavaScript almost always; it skips coercion entirely and behaves the way you’d expect.null (explicitly empty) from undefined (declared but never assigned, or simply doesn’t exist). PHP only has null for both cases."5" + 1 gives "51" (string concatenation wins), but "5" - 1 gives 4 (numeric coercion wins). Worth deliberately learning a few of these rather than guessing under pressure.PHP’s array is one flexible, ordered map used for everything — numeric lists, associative data, all of it. JavaScript splits this into two types:
Array — ordered lists, indexed by number.Object (or Map) — key/value data.You have to pick one on purpose instead of getting both behaviors for free from a single type.
Array helper functions exist in both languages, but the calling convention flips:
// PHP: callback comes first, array second$doubled = array_map(fn($n) => $n * 2, $numbers);// JavaScript: method lives on the array, callback comes firstconst doubled = numbers.map(n => n * 2);Concatenation is +, not .:
const greeting = "Hello, " + name + "!";Template literals are the more common approach in modern JS, and read close to PHP’s double-quoted string interpolation — just with different delimiters:
const greeting = `Hello, ${name}!`;thisvar is function-scoped and mostly legacy at this point. Use let (reassignable) and const (not reassigned) instead — they’re block-scoped, which matches what a PHP developer would already expect from { } blocks.this is where PHP’s $this stops being a reliable guide. In PHP, $this inside a method always refers to the object the method was called on — simple and consistent. In JavaScript, what this refers to depends on how the function was called, not where it was defined, and it can change unexpectedly when a function is passed around as a callback.class Counter { count = 0; increment() { this.count++; }}That looks safe, but pass counter.increment as a callback (e.g. to setTimeout or an event listener) and this can stop pointing at counter altogether. Arrow functions sidestep the problem by not having their own this — they inherit it from the surrounding scope, which is why they show up everywhere in modern JS, especially for callbacks:
button.addEventListener('click', () => this.increment());ES6 class syntax looks a lot like a PHP class — constructors, methods, extends — but it’s syntactic sugar over JavaScript’s underlying prototypal inheritance, not a separate class system the way PHP’s is. Day-to-day usage feels familiar; just don’t expect identical machinery underneath if you go looking for it.
npm and package.json play the role of Composer and composer.json.console.log() is the debugging equivalent of var_dump() / echo, but the output goes to the browser’s DevTools console (or the terminal, in Node), not the rendered page.The event loop
JavaScript doesn’t run top-to-bottom-and-done the way a PHP request does. Code reacts to events and can run long after the page first loads — this reframes almost everything else.
this is dynamic
Unlike PHP’s $this, JavaScript’s this depends on how a function is called, not where it’s defined. Prefer arrow functions for callbacks until this stops surprising you.
Everything else on this page is closer to “different syntax for a familiar idea.” Those two are actual shifts in mental model — worth the extra attention.