Table of Contents
- let and const: Block-Scoped Variables
- Arrow Functions: Concise Syntax for Functions
- Template Literals: String Interpolation & Multi-Line Strings
- Destructuring Assignment: Extract Values with Ease
- Default Parameters: Handle Missing Arguments Gracefully
- Rest and Spread Operators: Flexible Array/Object Handling
- Classes: Syntactic Sugar for Prototypal Inheritance
- Modules: Organize Code with import/export
- Promises: Asynchronous Programming Made Simple
- Enhanced Object Literals
- for…of Loop: Iterate Over Iterables
- Symbols: Unique Identifiers
1. let and const: Block-Scoped Variables
Before ES6, JavaScript had only function-scoped variables (declared with var), which often led to unexpected behavior due to hoisting and global leakage. ES6 introduced let and const to enforce block scoping (variables are limited to the block, statement, or expression they’re defined in).
let: Mutable Block-Scoped Variables
- Block-scoped: Limited to the
{ }block (e.g.,if,for, or function bodies) where they’re declared. - No hoisting to the top of the block: Unlike
var,letvariables are not accessible before their declaration (temporal dead zone). - Cannot be redeclared: A
letvariable cannot be declared again in the same scope.
Example: Fixing var Hoisting Issues
// With var (function-scoped)
if (true) {
var x = 10;
}
console.log(x); // 10 (leaks to global scope)
// With let (block-scoped)
if (true) {
let y = 20;
}
console.log(y); // Error: y is not defined (block-scoped)
const: Immutable Block-Scoped Variables
- Block-scoped: Same as
let, but with immutability. - Cannot be reassigned: Once declared, a
constvariable’s reference cannot change. - Must be initialized: Unlike
let,constrequires an initial value. - Note: For objects/arrays,
constprevents reassignment, but properties/elements can still be modified.
Example: Using const for Immutable References
const PI = 3.14159;
PI = 3; // Error: Assignment to constant variable
const user = { name: "Alice" };
user.name = "Bob"; // Valid: Only the reference is immutable
console.log(user); // { name: "Bob" }
Why It Matters: let and const eliminate accidental global variables and make code intent clearer (const for values that won’t change, let for mutable values).
2. Arrow Functions: Concise Syntax for Functions
Arrow functions (=>) provide a shorter syntax for writing function expressions, with key differences from traditional functions: lexical this binding, no arguments object, and inability to act as constructors.
Key Features
- Shorter syntax: Omit
functionandreturn(for single expressions). - Lexical
this: Inheritsthisfrom the surrounding scope (avoidsvar self = thishacks). - No
argumentsobject: Use rest parameters instead.
Syntax Examples
Basic Arrow Function
// Traditional function expression
const add = function(a, b) {
return a + b;
};
// Arrow function (explicit return)
const add = (a, b) => {
return a + b;
};
// Arrow function (implicit return, single expression)
const add = (a, b) => a + b; // Omit {} and return
Single Parameter (Omit Parentheses)
const square = x => x * x; // No need for (x)
No Parameters (Use Empty Parentheses)
const getTime = () => new Date().toLocaleTimeString();
Lexical this Binding
// Traditional function: `this` refers to the caller
const timer = {
seconds: 0,
start: function() {
setInterval(function() {
this.seconds++; // `this` is global/window (error!)
console.log(this.seconds);
}, 1000);
}
};
// Arrow function: `this` inherits from `timer`
const timer = {
seconds: 0,
start: function() {
setInterval(() => {
this.seconds++; // `this` is `timer` (correct!)
console.log(this.seconds);
}, 1000);
}
};
Why It Matters: Arrow functions simplify code and fix common this binding issues in callbacks (e.g., event handlers, timers).
3. Template Literals: String Interpolation & Multi-Line Strings
Template literals use backticks (`) instead of quotes (' or "), enabling string interpolation, multi-line strings, and tagged templates.
Key Features
- String interpolation: Embed expressions with
${expression}. - Multi-line strings: No need for
\n(line breaks are preserved). - Tagged templates: Advanced use case for parsing template literals (e.g., sanitization).
Examples
String Interpolation
const name = "Alice";
const age = 30;
// Traditional concatenation
const greeting = "Hello, my name is " + name + " and I'm " + age + " years old.";
// Template literal
const greeting = `Hello, my name is ${name} and I'm ${age} years old.`;
console.log(greeting); // "Hello, my name is Alice and I'm 30 years old."
Multi-Line Strings
// Traditional (messy with \n)
const poem = "Roses are red\nViolets are blue\nSugar is sweet\nAnd so are you";
// Template literal (preserves line breaks)
const poem = `Roses are red
Violets are blue
Sugar is sweet
And so are you`;
Expressions in Interpolation
const price = 19.99;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;
console.log(total); // "Total: $21.59"
Why It Matters: Eliminates messy string concatenation and makes multi-line strings readable.
4. Destructuring Assignment: Extract Values with Ease
Destructuring lets you extract values from arrays or objects into variables in a single line, with support for default values and nested structures.
Array Destructuring
Basic Extraction
const [first, second] = [10, 20];
console.log(first); // 10, second; // 20
Skip Elements
const [a, , b] = [1, 2, 3]; // Skip index 1
console.log(a); // 1, b; // 3
Default Values
const [x, y = 5] = [10]; // y defaults to 5 if undefined
console.log(x); // 10, y; // 5
Object Destructuring
Basic Extraction
const user = { name: "Bob", age: 25 };
const { name, age } = user; // Variables match object keys
console.log(name); // "Bob", age; // 25
Rename Variables
const { name: userName, age: userAge } = user;
console.log(userName); // "Bob", userAge; // 25
Nested Destructuring
const data = {
user: { id: 1, name: "Alice" },
posts: ["Post 1", "Post 2"]
};
const { user: { name }, posts: [firstPost] } = data;
console.log(name); // "Alice", firstPost; // "Post 1"
Why It Matters: Destructuring simplifies extracting data from APIs, state objects, or arrays, reducing boilerplate.
5. Default Parameters: Handle Missing Arguments
ES6 allows setting default values for function parameters, avoiding manual checks for undefined.
Syntax
// Traditional ES5: Check for undefined
function greet(name) {
name = name || "Guest"; // Fails if name is falsy (e.g., "")
console.log(`Hello, ${name}`);
}
// ES6: Default parameter
function greet(name = "Guest") {
console.log(`Hello, ${name}`);
}
greet(); // "Hello, Guest"
greet("Alice"); // "Hello, Alice"
Advanced: Defaults with Expressions
function getTotal(price, tax = price * 0.08) {
return price + tax;
}
console.log(getTotal(100)); // 108 (tax = 8)
Why It Matters: Cleaner than || checks and ensures parameters always have a value.
6. Rest and Spread Operators
The rest operator (...) collects multiple values into an array, while the spread operator expands an array/object into individual elements.
Rest Operator
Collect Function Arguments
function sum(...numbers) { // Rest collects arguments into array
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
Collect Array Elements
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1, rest; // [2, 3, 4]
Spread Operator
Copy Arrays
const original = [1, 2, 3];
const copy = [...original]; // Shallow copy
copy.push(4);
console.log(original); // [1, 2, 3] (unchanged)
Merge Arrays
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
Spread in Function Calls
const numbers = [1, 2, 3];
Math.max(...numbers); // 3 (same as Math.max(1, 2, 3))
Spread Objects (ES2018, but widely used with ES6)
const user = { name: "Alice" };
const adminUser = { ...user, role: "admin" }; // { name: "Alice", role: "admin" }
Why It Matters: Rest simplifies handling variable arguments; spread simplifies copying/merging arrays/objects without mutating originals.
7. Classes: Syntactic Sugar for Prototypal Inheritance
ES6 introduced class syntax, a cleaner way to work with JavaScript’s prototype-based inheritance (replacing constructor functions).
Basic Class Syntax
class Person {
constructor(name, age) { // Constructor initializes instances
this.name = name;
this.age = age;
}
greet() { // Method (added to prototype)
return `Hello, I'm ${this.name}`;
}
static isAdult(age) { // Static method (attached to class, not instances)
return age >= 18;
}
}
const alice = new Person("Alice", 30);
alice.greet(); // "Hello, I'm Alice"
Person.isAdult(20); // true
Inheritance with extends
class Student extends Person {
constructor(name, age, major) {
super(name, age); // Call parent constructor
this.major = major;
}
study() {
return `${this.name} is studying ${this.major}`;
}
}
const bob = new Student("Bob", 20, "Computer Science");
bob.greet(); // "Hello, I'm Bob" (inherited from Person)
bob.study(); // "Bob is studying Computer Science"
Why It Matters: Classes make inheritance more readable, aligning JavaScript with other OOP languages.
8. Modules: Organize Code with import/export
ES6 introduced native modules (import/export) to split code into reusable files, replacing CommonJS (require) or AMD.
Exporting from a Module
Named Exports (Multiple per File)
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
Default Export (One per File)
// user.js
export default class User {
constructor(name) {
this.name = name;
}
}
Importing from a Module
Import Named Exports
import { add, subtract } from './math.js';
add(2, 3); // 5
Import Default Export
import User from './user.js';
const user = new User("Alice");
Import All as Object
import * as MathUtils from './math.js';
MathUtils.add(2, 3); // 5
Why It Matters: Modules enable code organization, reusability, and tree-shaking (removing unused code in bundlers like Webpack).
9. Promises: Asynchronous Programming
Promises simplify handling asynchronous operations (e.g., API calls) by replacing callback hell with a chainable syntax.
Promise States
- Pending: Initial state (operation in progress).
- Fulfilled: Operation succeeded (call
resolve). - Rejected: Operation failed (call
reject).
Basic Promise
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("Data fetched!"); // Fulfilled
} else {
reject("Error fetching data"); // Rejected
}
}, 1000);
});
// Consume promise with .then() and .catch()
fetchData
.then(data => console.log(data)) // "Data fetched!"
.catch(error => console.error(error))
.finally(() => console.log("Operation complete")); // Runs on both success/failure
Chaining Promises
fetchData
.then(data => {
console.log(data);
return data.toUpperCase(); // Pass value to next .then()
})
.then(upperData => console.log(upperData)) // "DATA FETCHED!"
.catch(error => console.error(error));
Why It Matters: Promises make async code linear and readable, avoiding nested callbacks.
10. Enhanced Object Literals
ES6 simplifies object creation with shorthand properties, computed property names, and method definitions.
Shorthand Properties
const name = "Alice";
const age = 30;
// ES5: { name: name, age: age }
const user = { name, age }; // Shorthand: { name, age }
Computed Property Names
const key = "role";
const user = {
name: "Bob",
[key]: "admin" // Computed property: { name: "Bob", role: "admin" }
};
Method Definitions
const calculator = {
add(a, b) { // Omit `function` and `:`
return a + b;
}
};
calculator.add(2, 3); // 5
Why It Matters: Reduces redundancy and makes object literals more expressive.
11. for…of Loop: Iterate Over Iterables
The for...of loop iterates over iterable objects (arrays, strings, maps, sets) and returns values, unlike for...in (which returns keys).
Example with Arrays
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit); // "apple", "banana", "cherry"
}
Example with Strings
const str = "hello";
for (const char of str) {
console.log(char); // "h", "e", "l", "l", "o"
}
Why It Matters: Cleaner than for loops and safer than for...in (avoids iterating over object prototype properties).
12. Symbols: Unique Identifiers
Symbols are new primitive values representing unique, immutable identifiers, often used as object property keys to avoid name collisions.
Creating Symbols
const id = Symbol("id"); // "id" is a description (not used for equality)
const id2 = Symbol("id");
console.log(id === id2); // false (Symbols are unique)
Using Symbols as Object Keys
const user = {
[id]: 123, // Symbol key (hidden from normal iteration)
name: "Alice"
};
console.log(user[id]); // 123 (access with Symbol)
Why It Matters: Symbols enable “hidden” properties (not enumerable in for...in or Object.keys), useful for library design.
Conclusion
ES6 transformed JavaScript into a more powerful, readable, and maintainable language. Features like let/const, arrow functions, destructuring, and promises are now foundational for frontend development, enabling cleaner code and better handling of modern web challenges.
By mastering these features, you’ll write more efficient code, integrate seamlessly with frameworks like React and Vue, and stay current with industry best practices.