ES6, or ES2015 as it's officially called now, brought a long list of new JavaScript syntax this year, and we've started compiling our code through Babel so we can use some of it today without waiting for every browser to catch up natively. Getting the toolchain in place took a bit of trial and error, but the actual syntax changes have been the more interesting part of the year, once we stopped treating the whole spec as one big all-or-nothing adoption decision and started asking, feature by feature, whether it solves a problem we actually have.
What we reach for constantly
- let and const instead of var, mostly for the saner scoping, block-scoped rather than function-scoped, which has already caught a couple of bugs where a loop variable was accidentally being shared across iterations in a way var made easy to do by accident.
- Arrow functions, especially inside array methods where the shorter syntax and lexical this genuinely reduce bugs, no more var self = this sprinkled through callback-heavy code just to keep this pointing at the right object.
- Template literals instead of string concatenation, small thing, big readability win, especially for anything with more than two or three variables mixed into a string, where the old plus-sign concatenation style was getting genuinely hard to proofread.
Destructuring, once we understood where it helps
Destructuring didn't click for us immediately from the spec examples alone, it looked like syntax for its own sake at first. It clicked once we started pulling several properties off a single API response object, what used to be four or five separate lines assigning var name = response.name, var email = response.email, and so on, collapsed into a single destructuring assignment that also makes it obvious at a glance exactly which fields a function actually cares about. We've started using it heavily in function parameters too, destructuring an options object directly in the parameter list rather than accepting one opaque object and reading properties off it line by line inside the function body.
Default parameters replacing our old undefined checks
A small one, but a genuinely satisfying cleanup: default parameter values replace a pattern we'd written dozens of times, checking whether an argument was undefined and assigning a fallback on the next line. Being able to write that fallback directly in the function signature removes a line of boilerplate from nearly every utility function we've touched this year, and it reads more like documentation of the function's actual contract than the old defensive-check version did.
What we're still cautious about
Classes and modules we're using more sparingly, partly because our jQuery-heavy codebases don't always benefit from the restructuring, and partly because the tooling around ES6 modules still feels like it's settling, we've seen at least three different opinions online about the right way to bundle them for the browser, and none of them feels like the obviously correct answer yet. Worth adopting gradually rather than rewriting everything at once, and we're deliberately not forcing modules onto an existing jQuery plugin-heavy project just because the syntax is available now.
The Babel setup itself
Getting Babel wired into our existing Gulp pipeline took longer than we expected, mostly configuration rather than anything conceptually hard, choosing the right preset, wiring source maps through correctly so a bug in compiled code still points back to a sensible line number in the original ES6 source rather than the generated output. Source maps in particular felt like a nonnegotiable requirement once we tried debugging compiled code without them for an afternoon and gave up, staring at generated code that bore only a loose resemblance to what we'd actually written.
A migration mistake we made
We got overeager on one project and converted an entire existing jQuery-heavy file to use arrow functions wholesale, including inside jQuery event handlers, without noticing that jQuery relies on this inside a handler to refer to the element the event fired on, something arrow functions deliberately don't provide since they inherit this lexically from their surrounding scope instead. Every click handler in that file broke in the same confusing way, and it took an embarrassingly long debugging session to realize the arrow function conversion itself was the actual cause rather than some unrelated regression. It's now a specific thing we check for during any ES6 conversion, does this function rely on jQuery's own binding of this, before reaching for an arrow function as a reflexive syntax upgrade.
How this changes code review between the two of us
With both of us now writing a mix of new ES6 syntax and older ES5-style code side by side, code review conversations have shifted a bit, we're spending some review time simply agreeing on which patterns we've adopted and which we haven't yet, rather than assuming shared conventions the way we could when there was only one way to write a for loop or declare a variable. We've started keeping a short internal style note, nothing formal, just a running list of "we do this now" decisions, so a review comment about var versus const isn't relitigated project by project.
Where we're taking this next
We expect the cautious list, classes and modules specifically, to shrink over the next year as tooling matures and as we get more comfortable with the patterns on lower-stakes internal projects before trusting them on client work. For now, the features that solve an immediate, concrete problem, scoping, string building, parameter handling, are earning their place in daily use, and the more structural features are earning a slower, more deliberate evaluation before we lean on them the same way.
Spread and rest syntax, a smaller but genuinely useful addition
Alongside the features we listed as constant habits, spread syntax has quietly become one of our most-used small conveniences, copying an array or merging a couple of option objects together without reaching for a utility library function just for that. Rest parameters solved a specific annoyance in a handful of utility functions that used to inspect the arguments object directly, an array-like but not-quite-array object that always needed converting before we could actually use array methods on it, whereas a rest parameter just gives us a real array from the start.
For-of loops replacing a specific class of bug
We also picked up for-of loops for iterating over arrays where we don't need the index, which turned out to quietly fix a bug pattern we hadn't fully noticed we kept writing, a regular for loop with an off-by-one error in its bounds check, usually from copy-pasting a loop from elsewhere and forgetting to update the length variable it was checking against. A for-of loop has no bounds to get wrong in the first place, and switching to it wherever we don't need the index removed an entire category of small, easy-to-miss bugs from new code without us having to be more careful, just by removing the opportunity for that mistake to happen at all.
Promises, and why we're adopting them more cautiously than the syntax features
Native Promises are part of this same ES2015 wave, and they're the one feature on this list we're deliberately holding at arm's length a bit longer than the others, not because the syntax is hard, but because a fair amount of our existing asynchronous code is already built around jQuery's own Deferred objects and callback patterns, and mixing paradigms within the same codebase, some async code using Promises, some still using older callback patterns, has already caused a little confusion about which style a given function actually follows. We're converting new code to Promises deliberately, but we're not doing a wholesale retrofit of older callback-based code purely for consistency's sake, since that kind of change carries real regression risk for a benefit that's mostly aesthetic on code that already works.
What surprised us most about the transition overall
If we had to name the single most surprising thing about adopting ES6 this year, it's how much of the benefit came from features that sound almost too small to matter on their own, block scoping, template strings, default parameters, rather than from the more heavily marketed structural features like classes and modules. It's a reminder that a language update's biggest practical wins for a small team doing client work aren't always the ones that get the most conference talks, and it's changed how we evaluate the next wave of proposed JavaScript features too, asking "does this remove a small daily annoyance" rather than only "does this sound architecturally significant" when deciding what's worth adopting early.