I have been writing JavaScript since the late 2000s and for most of that time it felt like working around the language rather than with it. Verbose, inconsistent, and full of traps for the unwary. ES6, officially finalised in June 2015, was the biggest update to the language since its creation and it felt like JavaScript had finally grown up.
The features came all at once, which made the first few months of ES6 feel like learning a new language. But the core improvements addressed real pain points.
Arrow functions were perhaps the most immediately useful change. Before ES6, this was a constant source of confusion. Arrow functions lexically bind this, which means they inherit it from their surrounding scope rather than having their own. Half the bugs I had written involving callbacks and event handlers were related to this context. Arrow functions eliminated most of them.
Let and const replaced var. Variable hoisting in JavaScript had caused countless subtle bugs. Let and const are block-scoped rather than function-scoped, which is how every other language works and what you expect. Const additionally prevents reassignment, making code intentions clearer.
Destructuring and spread operators made working with objects and arrays dramatically cleaner. What previously required three lines of property extraction could be done in one. Default parameters eliminated the pattern of checking for undefined at the top of every function.
Promises were the big one for async code. JavaScript's callback-heavy style had created what developers called "callback hell": deeply nested callbacks that were difficult to read and nearly impossible to handle errors in. Promises provided a standard, composable way to handle async operations. The code became linear and the error handling became predictable.
Template literals solved string concatenation. Modules provided a standard way to organise code that did not require choosing between AMD and CommonJS.
Classes gave JavaScript a syntax for object-oriented patterns that matched what developers coming from other languages expected. Under the hood, nothing changed. JavaScript still used prototype-based inheritance. But the class syntax was more readable and less surprising.
The tooling ecosystem grew around ES6 quickly. Babel transpiled ES6 to ES5 for browsers that had not implemented the new features yet. Build tools like Webpack and Rollup handled modules. By the end of 2015, writing modern JavaScript meant writing ES6 and letting the build toolchain handle compatibility.
Looking back from years later, ES6 was the inflection point where JavaScript became a serious language for building serious applications. The annual release cadence that followed kept the language moving forward. But ES6 was the foundation everything else was built on.