There was a moment when I opened an old JavaScript file full of var, long anonymous callbacks, and string concatenations… and honestly, it felt like reading ancient text. After switching that project to ES6+ features, the code became shorter, clearer, and way less bug-prone.
If you’ve ever felt your JS looks “heavy” or hard to follow, modern ES6+ syntax is the upgrade your brain will thank you for.
Why ES6+ Changes How We Write JavaScript
ES6+ (also called “modern JavaScript”) isn’t just about fancy syntax. It solves real pain:
- Accidental bugs from var and weird scoping.
- Messy string concatenation.
- Callback hell and unreadable async code.
- Huge objects and arrays that are annoying to update.
let and const: Fewer “Why is this value changing?” Moments
Old JavaScript used var everywhere, which is function-scoped and can be re-declared accidentally. That leads to sneaky bugs. ES6 gave us:
- let – changeable variable.
- const – value cannot be reassigned.
let counter = 0;
counter = 1; // fine
const apiUrl = 'https://api.example.com';
// apiUrl = '...' not allowed
In React and Angular, a simple rule helps a lot:
- Use const by default
- Only use let when you really need to reassign.
Arrow Functions: Short and Sweet
Arrow functions are just functions with less noise:
// Before
function double(x) {
return x * 2;
}
// ES6+
const double = x => x * 2;
In React:
const UserList = ({ users }) => (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
Arrow functions are perfect for array methods like map, filter, and reduce, and they help avoid confusion with this in callbacks.
Template Literals: Strings That Don’t Hurt
No more "Hello " + name + "!" chaos.
const name = 'Alice';
const greeting = `Hello, ${name}!`;
You can even create multi-line strings without ugly \n everywhere. This is great for logging, debugging, and dynamic messages in both React and Angular apps.
Destructuring: Pick Only What You Need
Destructuring lets you “unpack” objects and arrays in one line.
const user = { id: 1, name: 'Alice', role: 'admin' };
const { name, role } = user;
// name = 'Alice', role = 'admin'
React props:
const UserCard = ({ name, role }) => (
<p>{name} – {role}</p>
);
Angular:
const { id } = this.route.snapshot.params;
It keeps your code focused and avoids repeating user.name, user.role all over the place.
Rest and Spread: The “…” You See Everywhere
That triple dot ... is one of the most powerful additions.
Spread (copy/merge):
const baseUser = { name: 'Alice', role: 'user' };
const adminUser = { ...baseUser, role: 'admin' }; // override role
const numbers = [1, 2];
const more = [...numbers, 3, 4]; // [1, 2, 3, 4]
Rest (collect “the rest”):
const { password, ...safeUser } = user; // everything except password
function logAll(first, ...rest) {
console.log(first, rest);
}
In React and Angular, spread is your best friend for immutable state updates. We have a detailed article on this topic, "Spread vs Rest in JavaScript: Understand the Difference with Real-World Examples" Don't forget to check it out.
Default Parameters: Safer Functions
You no longer need manual “fallback” logic:
function greet(name = 'Guest') {
return `Hello, ${name}!`;
}
greet(); // "Hello, Guest!"
greet('Sam'); // "Hello, Sam!"
This helps when you call functions from multiple places and don’t want to crash if a value is missing.
Classes: Cleaner Structure for Components and Services
ES6 classes make object-oriented patterns less painful:
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, I'm ${this.name}`;
}
}
const user = new User('Alice');
user.greet(); // "Hi, I'm Alice"
Angular leans on classes heavily (components, services). ES6 classes just make that pattern more obvious and readable.
Modules: import / export for Real Projects
Modules let you split logic into files and reuse code easily:
// userService.js
export function getUser() { /* ... */ }
export const USER_ROLES = ['admin', 'user'];
// main.js
import { getUser, USER_ROLES } from './userService.js';
React components, Angular services, utility functions—all of these benefit from clean module boundaries. To know more, check our article JavaScript CommonJS vs ES Modules: Import, Export, Migration, and Developer Best Practices.
Async/Await: No More Callback Hell
This is one of the biggest quality-of-life improvements.
Before:
fetch('/api/users')
.then(res => res.json())
.then(users => console.log(users));
With async/await:
async function loadUsers() {
const res = await fetch('/api/users');
const users = await res.json();
console.log(users);
}
In React and Angular, this makes side effects and data fetching much easier to follow and debug.
Bonus: Optional Chaining and Nullish Coalescing
These two tiny features remove a lot of “if checks.”
Optional chaining (?.):
const city = user.address?.city;
// No error if address is undefined or null
Nullish coalescing (??):
const count = input ?? 0; // Use 0 only if input is null or undefined
Perfect for working with API data where some fields might be missing.
Recommendation: What is a Nullish coalescing operator '??'
Conclusion: ES6+ Is Not “Fancy” — It’s Practical
When ES6+ first came out, it felt like “extra” syntax. But after using it in real Angular and React projects, it stopped being “nice-to-have” and became “how did I ever live without this?”
- let/const make bugs easier to avoid.
- Arrow functions and destructuring make code shorter and clearer.
- Spread/rest make state updates predictable.
- Async/await makes async code readable.
- Optional chaining and defaults make app code safer.
