Class 14: ES6+ Features & Modern JavaScript
JavaScript is a constantly evolving language. ECMAScript 2015, often referred to as ES6, introduced a significant set of new features and syntax enhancements that revolutionized how JavaScript is written. Subsequent versions (ES7, ES8, ES9, etc., collectively ES6+) have continued to add powerful capabilities. In this class, we will explore some of the most important and frequently used modern JavaScript features that are essential for writing clean, efficient, and expressive code today.
Arrow Functions
Arrow functions (=>
) provide a more concise syntax for writing function
expressions. They also handle the
this
keyword
differently than traditional functions, lexically binding
this
(i.e.,
this
refers to the
this
value of the
enclosing execution context).
Basic Syntax
//Traditional function expression
const addOld = function (a, b) {
return a + b;
};
console.log(addOld(2, 3));
//Arrow function equivalent
const add = (a, b) => a + b; //Implicit return for single expression
console.log(add(2, 3));
//Arrow function with no parameters
const greet = () => "Hello, World!";
console.log(greet());
//Arrow function with multiple statements (requires curly braces and explicit return)
const operate = (x, y) => {
let sum = x + y;
let product = x * y;
return { sum, product };
};
console.log(operate(10, 5));
Destructuring Assignment (Array and Object)
Destructuring assignment is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Array Destructuring
// Array Destructuring
const colors = ["red", "green", "blue"];
// Traditional way
// const c1 = colors[0];
// const c2 = colors[1];
// Array Destructuring
const [firstColor, secondColor, thirdColor] = colors;
console.log(firstColor);
console.log(secondColor);
// Skipping elements
const [, , lastColor] = colors;
console.log(lastColor);
// With default values
const [a, b, c, d = "purple"] = ["alpha", "beta", "gamma"];
console.log(a, b, c, d);
// Swapping variables easily
let x = 10;
let y = 20;
[x, y] = [y, x];
console.log(x, y);
Object Destructuring
// Object Destructuring
const person = {
name: "Alice",
age: 30,
city: "New York",
};
// Traditional way
// const name = person.name;
// const age = person.age;
const { name, age } = person;
console.log(name);
console.log(age);
// Renaming variables
const { city: town } = person;
console.log(town);
// With default values
const { job = "Unemployed", age: personAge } = person;
console.log(job);
console.log(personAge);
// Nested object destructuring
const user = {
id: 1,
details: {
firstName: "Bob",
lastName: "Smith",
},
};
const {
details: { firstName, lastName },
} = user;
console.log(firstName, lastName);
Spread and Rest Operators (...
)
The ...
syntax serves
two distinct but related purposes: the Spread operator and the
Rest parameter.
Spread Operator (...
)
Expands an iterable (like an array or string) into individual elements, or an object into key-value pairs, where multiple elements/properties are expected.
- Copying Arrays: Creates a shallow copy of an array.
- Concatenating Arrays: Combines arrays easily.
- Adding elements to Arrays: Inserting elements at any position.
- Spreading function arguments: Passing array elements as individual arguments.
- Copying and Merging Objects: Creates shallow copies or merges properties of objects.
// Spreading Arrays
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2);
const combined = [...arr1, ...[6, 7]]; // Concatenate arrays
console.log(combined);
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// Spreading Objects
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2);
const obj3 = { b: 5, d: 4 };
const merged = { ...obj1, ...obj3 };
console.log(merged);
Rest Parameter (...
)
Collects an indefinite number of arguments into an array. It must be the last parameter in a function definition.
// Rest Parameter (...)
function calculateAverage(name, ...grades) {
// 'grades' collects all remaining arguments into an array
const total = grades.reduce((sum, grade) => sum + grade, 0);
return `${name}'s average: ${total / grades.length}`;
}
console.log(calculateAverage("John", 90, 85, 92, 88));
console.log(calculateAverage("Jane", 70, 75));
// Rest in destructuring
const [first, second, ...restOfColors] = [
"red",
"green",
"blue",
"yellow",
"purple",
];
console.log(first);
console.log(second);
console.log(restOfColors);
Template Literals
Template literals (backticks ``
) offer an easy way
to create multiline strings and perform string interpolation.
-
Multiline Strings: No need for
\n
or string concatenation across lines. -
String Interpolation: Embed expressions
directly within the string using
${expression}
.
// Template Literals
const productName = "Laptop";
const price = 1200;
const quantity = 2;
// Old way of concatenation
const oldSummary =
"Product: " +
productName +
"\n" +
"Price: $" +
price +
"\n" +
"Total: $" +
price * quantity;
console.log(oldSummary);
// Using Template Literals
const newSummary = `Product: ${productName}
Price: $${price}
Total: $${price * quantity}`;
console.log(newSummary);
JavaScript Modules (import
, export
)
ES Modules provide a standardized way to organize JavaScript code into reusable modules. This helps in breaking down large applications into smaller, manageable files, improving code organization, reusability, and maintainability.
-
export
: Used to export functions, variables, classes, etc., from a module. -
import
: Used to import exported members from other modules.
Typically, module files are saved with a
.js
extension, and
the script type in HTML must be
type="module"
.
math.js
(Module to be
exported)
<!-- math.js -->
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
<!-- Exporting a default (only one default export per module) -->
const multiply = (a, b) => a * b;
export default multiply;
app.js
(Module
consuming math.js
)
<!-- app.js -->
import { PI, add, subtract } from './math.js'; <!-- Named imports -->
import multiplyNumbers from './math.js'; <!-- Default import (can be named anything) -->
console.log("PI:", PI);
console.log("2 + 3 =", add(2, 3));
console.log("10 - 4 =", subtract(10, 4));
console.log("5 * 6 =", multiplyNumbers(5, 6));
In HTML (how to include modules)
<!-- index.html -->
<script type="module" src="app.js"></script>
Using modules helps manage dependencies, avoids global variable pollution, and enables tools to optimize your code (e.g., tree-shaking).
Introduction to OOP Principles
JavaScript is a multi-paradigm language, supporting both functional and object-oriented programming (OOP). OOP is a programming paradigm based on the concept of "objects", which can contain data and code.
- Encapsulation: Bundling data (properties) and methods that operate on the data within a single unit (an object), and restricting direct access to some of an object's components. This "hides" the internal implementation details.
- Inheritance: A mechanism where a new class (subclass/child class) is derived from an existing class (superclass/parent class), inheriting its properties and methods. This promotes code reuse.
- Polymorphism: The ability of objects of different classes to respond to the same method call in their own specific ways. It means "many forms."
Objects and Prototypes
JavaScript is a prototype-based language. Instead of traditional class-based inheritance, objects inherit properties and methods from other objects via a prototype chain. Every JavaScript object has a prototype.
// Object literal
const car = {
brand: "Toyota",
model: "Camry",
start: function() {
console.log(`${this.brand} ${this.model} started.`);
}
};
car.start();
// Prototype chain example
const animal = {
eats: true,
walk() {
console.log("Animal walks.");
}
};
const rabbit = {
jumps: true,
__proto__: animal // rabbit inherits from animal
};
rabbit.walk();
console.log(rabbit.eats);
Constructor Functions
Before ES6 classes, constructor functions were the primary way
to create "classes" or blueprints for objects in JavaScript.
They are regular functions that are invoked with the
new
keyword.
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
}
const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);
person1.greet();
person2.greet();
While functional, a common optimization was to add methods to the constructor's prototype to avoid creating a new function for every instance, saving memory.
function PersonOptimized(name, age) {
this.name = name;
this.age = age;
}
PersonOptimized.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const person3 = new PersonOptimized("Charlie", 40);
person3.greet();
ES6 Classes
ES6 introduced the class
keyword, which is
syntactic sugar over JavaScript's existing prototype-based
inheritance. It provides a cleaner and more familiar syntax for
creating objects and handling inheritance, making JavaScript OOP
more approachable for developers from class-based languages.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
const dog = new Animal("Dog");
dog.speak();
Inheritance with Classes
The extends
keyword is used to create a subclass
that inherits from a parent class. The super()
keyword is used within the subclass's constructor to call the
parent class's constructor, ensuring proper initialization of
inherited properties.
class Dog extends Animal { // Dog inherits from Animal
constructor(name, breed) {
super(name); // Call the parent (Animal) constructor
this.breed = breed;
}
// Override the speak method
speak() {
console.log(`${this.name} barks! It's a ${this.breed}.`);
}
fetch() {
console.log(`${this.name} is fetching the ball.`);
}
}
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.speak();
myDog.fetch();
console.log(myDog.name);
Static Methods and Properties
Static methods and properties belong to the class itself, rather than to any instance of the class. They are called directly on the class name and are often used for utility functions or properties that are relevant to the class as a whole, not a specific object.
class Calculator {
static PI = 3.14159; // Static property (ES2022)
static add(a, b) {
return a + b;
}
static subtract(a, b) {
return a - b;
}
}
console.log(Calculator.PI);
console.log(Calculator.add(10, 5));
console.log(Calculator.subtract(10, 5));
// Attempting to call a static method on an instance will fail
// const calc = new Calculator();
// calc.add(1, 2); // TypeError: calc.add is not a function
These modern JavaScript features are fundamental for writing clean, efficient, and maintainable code in today's web development landscape, especially when working with frameworks like React.