One of the most important lessons I've ever learned about Javascript is that no Javascript application is, by itself, actually a single program. Every Javascript application of even modest size is several different programs standing together in a trench coat and trying very hard to look like a single thing.
Let's look at the single most simple web application, the classic "push a button to increment a counter" that is the centerpiece of every "introduction to React / Vue / Angular / Lit / Svelte / etc." blogpost you've ever read. This application is just a button and a number, and every time you click the button the number increments. And let's talk about how it's very different from classical programming.
I'm going to write the "classical" version in Python, but it looks the same in every language* written since the introduction of Algol in 1958:
counter = 0
while True:
print("The counter is at:", counter)
print("Press the [Enter] button:", end="")
input()
counter = counter + 1
To the eye, this is one entire program. Here's a similar program in Lit:
class Counter extends LitElement {
@state()
counter = 0;
increment(ev: Event) {
ev.stopPropagation();
this.counter = this.counter + 1;
}
render() {
return html`<div>
<p>The counter is at: ${this.counter}</p>
<button @click=${this.increment}>Increase the count</button>
</div>`;
}
}
In some ways they look the same, but there's one thing missing from the second: the loop.
So if your reaction is "Well, of course, this is event-oriented, the browser is the loop," you're
halfway to the point of this blog post. There are actually two programs in here, they just happen to
share a context: the counter
.
Think about what happens in this program. In the first step, the page is rendered, the counter is constructed and the content rendered and connected to the DOM... and then it ends. That program is over. All that's left is a state data object with a single number in it.
And then, sometime later (milliseconds, hours, years, it doesn't matter) someone presses the button.
A second program runs. This program has a reference to that counter
, and when you press that
button it emits an event that is routed to the increment
function, which increases the counter by
one. Under the covers, though, that @state()
declaration issues an event: this object has changed
its state, which Lit then captures and schedules a re-render. You know what happens next? And then
it ends.
That's right, the program halts right there. Almost immediately afterward, within the space of a
single animation frame, the scheduled re-render is started. This is a completely different program
that repeats what happened in the very first run: it draws the contents of the render
function
with the new counter value. And then... yes, and then it ends.
I believe that understanding this principle, that every Javascript program is actually many independent programs, some of them sharing access to the same variables, all running in a series, sometimes interleaved by pauses mandated by scheduling algorithms, network event managers, animation timers, and anticipated user events, is a crucial mindset to getting JavaScript development right. If you can narrow down "exactly what changes this event will cause" and nail the handling of it, what it will do, and exclude everything else from your thinking, you'll be much happier in your approach to software development.
I don't want to say that you should think of your program as a series of unrelated events. Obviously, you want to think along the lines of "If I press the submit button, the form will bundle the contents of its inputs and send them to the server. If it's successful, I'll redirect to the next page, but if it fails I need to show the error messages." But before you can code that thought, break it down further, event by event, and understand how you will receive and handle each event, where the context for that event is stored, how it is accessed, and how it is changed. As for the consequences of that change, treat that as a different program.
"You're just talking about subroutines."
Yes, that's true. I've found that too many developers, myself included, have no problem thinking that way when it comes to writing classical code in languages like Rust or Python, but lose that focus within web development ... because humans. There is so much going on in a professional web application that it becomes easy to lose that focus or become overwhelmed by it. By internalizing the idea and then it ends into your head, you'll have reference points in your code where you can reason clearly about events, what happens when an event fires, what changes, and what to do next.