What Declarative Code Actually Means in JavaScript
Why the recent features added to JavaScript aren't a random grab bag, and the one word, declarative, that explains what almost all of them have in common.
The last post ended with a claim: nearly every feature added to JavaScript since 2015 exists to let you say what you want instead of how to get it. Here's what that actually looks like in code.
Not a Grab Bag, One Throughline
Look at a list of new JavaScript features, arrow functions, destructuring, optional chaining, and it can feel random. Like a grab bag of unrelated syntax someone kept bolting on.
It's not random. Almost all of it points the same direction: JavaScript is getting more declarative.
Declarative versus imperative is a distinction worth knowing by name, because you'll see it again constantly in this series. Imperative code spells out every step to reach a result. Declarative code just states the result, and leaves the steps to the language itself.
The Clearest Example: Calling a Function With an Array
Say you have an array of numbers and you want the largest one.
const scores = [12, 45, 3, 67, 21];
Math.max(scores); // NaN, it expects separate numbers, not one array
Math.max(12, 45, 3, 67, 21); // 67, this is what Math.max actually wantsMath.max never accepts an array directly, it wants each number handed over one at a time. So before ES6, you reached for apply, a method every function has:
const highest = Math.max.apply(null, scores);
// Math.max.apply(thisArg, argsArray) unpacks the array
// and calls Math.max with each item as its own argument,
// exactly like the line above, just built from the array.apply's real job is letting you control what this means inside the function you're calling. Math.max never uses this at all, so that first argument is just a placeholder nobody actually wants there, null because nothing meaningful goes in that slot. A reader has to already know that trick to see that this whole line just means "call Math.max with each item in scores."
The spread operator says exactly that, directly:
const highest = Math.max(...scores);Nothing here is a new capability. Both versions produce the same number. What changed is that the second version doesn't ask you to know a workaround first. It reads as what it does.
This Wasn't Guesswork, It Was Already Common Practice
None of this is JavaScript inventing tricks nobody had thought of. Developers were already spreading arrays into function calls and converting array-like objects into real arrays, using apply and Array.prototype.slice.call as workarounds, for years before ES6 shipped.
What changed is that the language finally gave a name and a clean syntax to something people were already hacking together. That's most of what this series covers: not new abilities, but honest syntax for patterns that already existed.
Keep that lens as the chapters ahead cover the spread operator properly, along with destructuring, new array methods, and generators. Each one will read as a small, isolated trick if you look at it alone. Held next to the others, they're the same idea, repeated: state the outcome, and let the language handle the mechanism.
That's also, honestly, why a language never stops changing once developers start hacking around a real gap. Someone finds the workaround first. The language catches up later, and turns it into syntax everyone can read.
One thing stands between here and the actual features: what happens if you still have to support an older browser. It's less of a blocker than it sounds.
Keep reading