ECMAScript ES6-ES15 Complete Guide: Modern JavaScript Syntax Evolution and Learning Path
ECMAScript ES6-ES15 Complete Guide: Modern JavaScript Syntax Evolution and Learning Path
ECMAScript (abbreviated as ES) is the standard specification for the JavaScript language, published by the ECMA international organization. Since ES6 (2015), ECMAScript has adopted an annual release cycle, with each version bringing new syntax features, performance improvements, and development experience enhancements. This article will detail all the important features from ES6 to ES15, providing you with a complete learning roadmap.
📋 ECMAScript Version Timeline
| Version | Release Year | Key Features | Importance |
|---|---|---|---|
| ES6/ES2015 | 2015 | let/const, Arrow Functions, Promises, Classes, Modules | ⭐⭐⭐⭐⭐ Revolutionary |
| ES7/ES2016 | 2016 | Array.includes(), Exponentiation Operator | ⭐⭐⭐ Practical Enhancement |
| ES8/ES2017 | 2017 | async/await, Object.values() | ⭐⭐⭐⭐ Async Programming Revolution |
| ES9/ES2018 | 2018 | Object Spread, Async Iteration | ⭐⭐⭐ Syntax Refinement |
| ES10/ES2019 | 2019 | Array.flat(), Object.fromEntries() | ⭐⭐⭐ Utility Enhancement |
| ES11/ES2020 | 2020 | Optional Chaining, Nullish Coalescing, Dynamic Import | ⭐⭐⭐⭐ Developer Experience |
| ES12/ES2021 | 2021 | Logical Assignment, Numeric Separators | ⭐⭐⭐ Syntax Sugar |
| ES13/ES2022 | 2022 | Private Fields, Top-level await | ⭐⭐⭐⭐ OOP Enhancement |
| ES14/ES2023 | 2023 | findLast, Hashbang Support | ⭐⭐ Development Tools |
| ES15/ES4 | 2024 | RegExp v mode, Array.fromAsync() | ⭐⭐⭐ Cutting-edge Features |
🚀 ES6 (2015): The Starting Point of Modern JavaScript
ES6 is the most important version update in JavaScript history, laying the foundation for modern frontend development.
Core Syntax Features
1. let and const: Block Scope
// let: Block-scoped variables
{
let name = 'JavaScript';
console.log(name); // 'JavaScript'
}
// console.log(name); // ReferenceError: name is not defined
// const: Constant declaration
const API_URL = 'https://api.example.com';
// API_URL = 'new-url'; // TypeError: Assignment to constant variable
// Object/array constants can still modify content
const user = { name: 'Alice' };
user.name = 'Bob'; // ✅ Allowed
// user = {}; // ❌ Not allowed
2. Arrow Functions
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
// Single parameter can omit parentheses
const double = x => x * 2;
// Multi-line function body
const createUser = (name, age) => {
return {
name,
age,
createdAt: new Date()
};
};
// this binding特性
const obj = {
name: 'Object',
traditional: function() {
setTimeout(function() {
console.log(this.name); // undefined (this points to global)
}, 100);
},
arrow: function() {
setTimeout(() => {
console.log(this.name); // 'Object' (inherits outer this)
}, 100);
}
};3. Template Literals
const name = 'World';
const age = 25;
// Basic interpolation
const message = `Hello, ${name}! You are ${age} years old.`;
// Multi-line strings
const html = `
<div class="user">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
// Expression evaluation
const price = 100;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;4. Destructuring Assignment
// Array destructuring
const [first, second, third] = [1, 2, 3];
const [, , , fourth] = [1, 2, 3, 4]; // Skip first three
// Object destructuring
const user = { name: 'Alice', age: 30, city: 'New York' };
const { name, age } = user;
// Renaming
const { name: userName, age: userAge } = user;
// Default values
const { name, country = 'Unknown' } = user;
// Nested destructuring
const data = {
user: {
profile: {
name: 'Bob',
email: '[email protected]'
}
}
};
const { user: { profile: { name, email } } } = data;
// Function parameter destructuring
function createUser({ name, age, city = 'Unknown' }) {
return { name, age, city };
}
const user1 = createUser({ name: 'Alice', age: 25 });5. Default Parameters and Spread Operator
// Default parameters
function greet(name = 'Guest', message = 'Hello') {
return `${message}, ${name}!`;
}
// Spread operator - Arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// Spread operator - Objects
const user = { name: 'Alice', age: 30 };
const userWithRole = { ...user, role: 'admin' };
// Rest parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3, 4, 5); // 15
// Spread operator in function calls
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3
6. Classes
// Basic class definition
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Instance method
greet() {
return `Hello, I'm ${this.name}`;
}
// Static method
static createAdult(name) {
return new Person(name, 18);
}
}
// Inheritance
class Employee extends Person {
constructor(name, age, position) {
super(name, age); // Call parent constructor
this.position = position;
}
// Override method
greet() {
return `${super.greet()} and I'm a ${this.position}`;
}
work() {
return `${this.name} is working as a ${this.position}`;
}
}
const employee = new Employee('Alice', 28, 'Developer');
console.log(employee.greet()); // "Hello, I'm Alice and I'm a Developer"
7. Modules (Import/Export)
// math.js - Export
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// Default export
export default function subtract(a, b) {
return a - b;
}
// main.js - Import
import subtract from './math.js'; // Default import
import { PI, add, Calculator } from './math.js'; // Named import
import * as MathUtils from './math.js'; // Import all
// Usage
console.log(PI);
console.log(add(2, 3));
const calc = new Calculator();
console.log(calc.multiply(4, 5));
console.log(subtract(10, 3));8. Promises: Foundation of Async Programming
// Creating Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation successful!');
} else {
reject('Operation failed!');
}
}, 1000);
});
// Using Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Operation completed'));
// Promise chaining
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('Data:', data);
return processData(data);
})
.then(processedData => {
console.log('Processed:', processedData);
})
.catch(error => {
console.error('Error:', error);
});
}
// Promise.all - Parallel execution
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
Promise.all([promise1, promise2])
.then(([users, posts]) => {
console.log('Users:', users);
console.log('Posts:', posts);
});
// Promise.race - Competitive execution
Promise.race([
fetch('/api/fast'),
fetch('/api/slow')
]).then(response => {
console.log('First response:', response);
});9. Map and Set: New Data Structures
// Map - Key-value pairs collection
const map = new Map();
map.set('name', 'Alice');
map.set(42, 'The answer');
map.set({}, 'Object key');
const user = { id: 1 };
map.set(user, 'User object');
// Get values
console.log(map.get('name')); // 'Alice'
console.log(map.get(42)); // 'The answer'
// Check if key exists
console.log(map.has('name')); // true
// Delete key
map.delete('name');
// Iterate Map
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
// Set - Unique values collection
const set = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(set); // Set {1, 2, 3, 4, 5}
// Add values
set.add(6);
set.add(1); // Duplicate values won't be added
// Check if value exists
console.log(set.has(3)); // true
// Delete values
set.delete(3);
// Iterate Set
for (const value of set) {
console.log(value);
}
// Practical application: Array deduplication
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)]; // [1, 2, 3, 4, 5]
📈 ES7 (2016): Small but Practical Enhancements
ES7, although with small changes, introduced two very practical features.
1. Array.prototype.includes()
const numbers = [1, 2, 3, 4, 5];
// Check if element exists
console.log(numbers.includes(3)); // true
console.log(numbers.includes(6)); // false
// Support starting position
console.log(numbers.includes(3, 2)); // true (search from index 2)
console.log(numbers.includes(1, 1)); // false (search from index 1)
// Comparison with indexOf
// indexOf returns position, includes returns boolean
console.log(numbers.indexOf(3) !== -1); // true (old way)
console.log(numbers.includes(3)); // true (new way, more intuitive)
// Special value handling
console.log([NaN].includes(NaN)); // true
console.log([NaN].indexOf(NaN) !== -1); // false (indexOf cannot handle NaN correctly)
2. Exponentiation Operator (**)
// Basic usage
console.log(2 ** 3); // 8 (2 to the power of 3)
console.log(10 ** 2); // 100
// Comparison with Math.pow()
console.log(2 ** 3); // 8
console.log(Math.pow(2, 3)); // 8 (old way)
// Negative exponent
console.log(2 ** -2); // 0.25
// Combined operations
const base = 2;
const exponent = 3;
const result = base ** exponent; // 8
// Usage in expressions
const area = radius => Math.PI * radius ** 2;
console.log(area(5)); // 78.53981633974483
// Right-associative
console.log(2 ** 3 ** 2); // 512 (equivalent to 2 ** (3 ** 2))
🔄 ES8 (2017): New Era of Async Programming
ES8 introduced async/await, completely changing the way JavaScript handles asynchronous programming.
1. Async/Await
// Basic syntax
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Using async function
async function main() {
try {
const data = await fetchData();
console.log('Data:', data);
} catch (error) {
console.error('Failed to fetch data:', error);
}
}
// Arrow function form
const fetchUserData = async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
};
// Parallel execution of multiple async operations
async function fetchMultipleData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
}
// Sequential execution of async operations
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// Async function returns Promise
async function getValue() {
return 42;
}
const promise = getValue(); // Returns Promise
promise.then(value => console.log(value)); // 42
2. Object.values() and Object.entries()
const user = {
name: 'Alice',
age: 30,
city: 'New York',
email: '[email protected]'
};
// Object.values() - Get all values
const values = Object.values(user);
console.log(values); // ['Alice', 30, 'New York', '[email protected]']
// Object.entries() - Get key-value pairs array
const entries = Object.entries(user);
console.log(entries);
// [
// ['name', 'Alice'],
// ['age', 30],
// ['city', 'New York'],
// ['email', '[email protected]']
// ]
// Practical application: Object transformation
const transformed = Object.entries(user).map(([key, value]) => ({
key,
value,
type: typeof value
}));
console.log(transformed);
// Object filtering
const filteredEntries = Object.entries(user)
.filter(([key, value]) => typeof value === 'string')
.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
console.log(filteredEntries); // { name: 'Alice', city: 'New York', email: '[email protected]' }
// Conversion with Map
const userMap = new Map(Object.entries(user));
console.log(userMap.get('name')); // 'Alice'
3. String.prototype.padStart() and padEnd()
// padStart() - Pad at the beginning of string
const str = '5';
console.log(str.padStart(3, '0')); // '005'
const name = 'Alice';
console.log(name.padStart(10, '*')); // '*****Alice'
// padEnd() - Pad at the end of string
console.log(str.padEnd(3, '0')); // '500'
console.log(name.padEnd(10, '*')); // 'Alice*****'
// Practical application: Number formatting
function formatNumber(num, length = 2) {
return String(num).padStart(length, '0');
}
console.log(formatNumber(5)); // '05'
console.log(formatNumber(12)); // '12'
console.log(formatNumber(123)); // '123' (doesn't truncate if exceeds length)
// Table alignment
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
users.forEach(user => {
const namePadded = user.name.padEnd(10, ' ');
const agePadded = String(user.age).padStart(3, ' ');
console.log(`${namePadded} | ${agePadded}`);
});
// Alice | 30
// Bob | 25
// Charlie | 35
🎯 ES9 (2018): Objects and Async Iteration
ES9 continued to improve JavaScript syntax features, especially in object operations and async programming.
1. Object Spread Operator
// Object spread
const user = { name: 'Alice', age: 30 };
const additionalInfo = { city: 'New York', country: 'USA' };
const completeUser = { ...user, ...additionalInfo };
console.log(completeUser);
// { name: 'Alice', age: 30, city: 'New York', country: 'USA' }
// Property override
const updatedUser = { ...user, age: 31 };
console.log(updatedUser); // { name: 'Alice', age: 31 }
// Shallow copy
const userCopy = { ...user };
console.log(userCopy === user); // false (different objects)
// Nested object considerations
const original = {
user: { name: 'Alice', age: 30 },
settings: { theme: 'dark' }
};
const copy = { ...original };
copy.user.name = 'Bob';
console.log(original.user.name); // 'Bob' (nested objects are still references)
// Deep copy requires other methods
const deepCopy = JSON.parse(JSON.stringify(original));
// Usage in functions
function updateUser(updates) {
const defaultUser = { name: 'Guest', age: 0, active: true };
return { ...defaultUser, ...updates };
}
const newUser = updateUser({ name: 'Alice', age: 25 });
console.log(newUser); // { name: 'Alice', age: 25, active: true }
2. Async Iteration (for-await-of)
// Async iterator
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
// Using for-await-of
async function processAsyncData() {
for await (const value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
}
// Async iterable object
const asyncIterable = {
async *[Symbol.asyncIterator]() {
let i = 1;
while (i <= 3) {
yield new Promise(resolve => {
setTimeout(() => resolve(i * 2), 1000);
});
i++;
}
}
};
async function processAsyncIterable() {
for await (const value of asyncIterable) {
console.log(value); // 2, 4, 6 (output one per second)
}
}
// Practical application: Paginated data fetching
async function* fetchPages(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
async function getAllItems(url) {
const allItems = [];
for await (const items of fetchPages(url)) {
allItems.push(...items);
}
return allItems;
}3. Promise.prototype.finally()
// Basic usage
fetch('/api/data')
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error))
.finally(() => console.log('Request completed'));
// Practical application: Loading state management
function showLoading() {
console.log('Loading...');
}
function hideLoading() {
console.log('Loading complete');
}
async function fetchDataWithLoading() {
showLoading();
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error;
} finally {
hideLoading(); // Executes regardless of success or failure
}
}
// Resource cleanup
function withResource(resource, callback) {
return resource
.then(callback)
.catch(error => {
console.error('Error:', error);
throw error;
})
.finally(() => {
// Clean up resource
resource.close && resource.close();
});
}🛠️ ES10 (2019): Convenient Enhancements for Arrays and Objects
ES10 introduced many practical array methods and object operation features.
1. Array.prototype.flat() and flatMap()
// flat() - Array flattening
const nestedArray = [1, [2, 3], [4, [5, 6]]];
console.log(nestedArray.flat()); // [1, 2, 3, [4, [5, 6]]]
// Specify flattening depth
console.log(nestedArray.flat(2)); // [1, 2, 3, 4, 5, 6]
// Complete flattening
const deeplyNested = [1, [2, [3, [4, [5]]]]];
console.log(deeplyNested.flat(Infinity)); // [1, 2, 3, 4, 5]
// flatMap() - Map and flatten
const sentences = [
'Hello world',
'How are you',
'Fine thank you'
];
const words = sentences.flatMap(sentence => sentence.split(' '));
console.log(words);
// ['Hello', 'world', 'How', 'are', 'you', 'Fine', 'thank', 'you']
// Comparison with map + flat
const result1 = sentences.map(s => s.split(' ')).flat();
const result2 = sentences.flatMap(s => s.split(' '));
console.log(result1.length === result2.length); // true
// Practical application: Data transformation
const data = [
{ id: 1, tags: ['javascript', 'nodejs'] },
{ id: 2, tags: ['react', 'javascript'] },
{ id: 3, tags: ['vue', 'nodejs'] }
];
const allTags = data.flatMap(item => item.tags);
const uniqueTags = [...new Set(allTags)];
console.log(uniqueTags); // ['javascript', 'nodejs', 'react', 'vue']
// Complex data processing
const orders = [
{ id: 1, items: [{ name: 'Book', price: 20 }, { name: 'Pen', price: 5 }] },
{ id: 2, items: [{ name: 'Laptop', price: 1000 }] }
];
const allItems = orders.flatMap(order =>
order.items.map(item => ({ ...item, orderId: order.id }))
);
console.log(allItems);
// [
// { name: 'Book', price: 20, orderId: 1 },
// { name: 'Pen', price: 5, orderId: 1 },
// { name: 'Laptop', price: 1000, orderId: 2 }
// ]
2. Object.fromEntries()
// Basic usage: Convert key-value pairs array to object
const entries = [
['name', 'Alice'],
['age', 30],
['city', 'New York']
];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'Alice', age: 30, city: 'New York' }
// Used with Object.entries()
const original = { name: 'Bob', age: 25, city: 'London' };
const transformed = Object.fromEntries(
Object.entries(original).map(([key, value]) => [
key.toUpperCase(),
typeof value === 'string' ? value.toUpperCase() : value
])
);
console.log(transformed);
// { NAME: 'BOB', AGE: 25, CITY: 'LONDON' }
// Practical application: Query parameter processing
const queryString = 'name=Alice&age=30&city=New+York';
const params = new URLSearchParams(queryString);
const paramsObj = Object.fromEntries(params);
console.log(paramsObj); // { name: 'Alice', age: '30', city: 'New York' }
// Object filtering
const user = {
name: 'Alice',
age: 30,
password: 'secret123',
email: '[email protected]',
token: 'abc123'
};
const publicUser = Object.fromEntries(
Object.entries(user).filter(([key]) =>
!['password', 'token'].includes(key)
)
);
console.log(publicUser);
// { name: 'Alice', age: 30, email: '[email protected]' }
// Map to object conversion
const map = new Map([
['name', 'Charlie'],
['age', 35]
]);
const mapObj = Object.fromEntries(map);
console.log(mapObj); // { name: 'Charlie', age: 35 }
3. String.prototype.trimStart() and trimEnd()
const message = ' Hello, World! ';
// trimStart() - Remove leading whitespace
console.log(message.trimStart()); // 'Hello, World! '
// trimEnd() - Remove trailing whitespace
console.log(message.trimEnd()); // ' Hello, World!'
// trim() - Remove both leading and trailing whitespace
console.log(message.trim()); // 'Hello, World!'
// Alias methods
console.log(message.trimLeft() === message.trimStart()); // true
console.log(message.trimRight() === message.trimEnd()); // true
// Practical application: User input processing
function cleanInput(input) {
return input.trimStart().trimEnd();
}
const userInput = ' [email protected] ';
const cleanEmail = cleanInput(userInput);
console.log(cleanEmail); // '[email protected]'
// Code formatting
function formatCode(code) {
return code
.split('\n')
.map(line => line.trimStart())
.join('\n');
}
const messyCode = `
function test() {
console.log('Hello');
}
`;
console.log(formatCode(messyCode));
// function test() {
// console.log('Hello');
// }
4. Optional catch Binding
// Traditional way: Error parameter required
try {
JSON.parse('invalid json');
} catch (error) {
console.log('Something went wrong');
}
// ES10: Can omit error parameter
try {
JSON.parse('invalid json');
} catch {
console.log('Something went wrong');
}
// Practical application: Simplified error handling
function safeParse(jsonString) {
try {
return JSON.parse(jsonString);
} catch {
return null;
}
}
console.log(safeParse('{"name": "Alice"}')); // { name: 'Alice' }
console.log(safeParse('invalid json')); // null
// Multiple try-catch blocks
async function processFiles(files) {
const results = [];
for (const file of files) {
try {
const content = await readFile(file);
const parsed = JSON.parse(content);
results.push(parsed);
} catch {
// Ignore error, continue processing next file
console.log(`Failed to process ${file}`);
}
}
return results;
}🔍 ES11 (2020): Optional Chaining and Nullish Coalescing
ES11 introduced many important features that enhance the development experience.
1. Optional Chaining Operator (?.)
const user = {
name: 'Alice',
address: {
street: '123 Main St',
city: 'New York'
}
};
// Traditional way: Multiple checks
const city = user && user.address && user.address.city;
console.log(city); // 'New York'
// Optional chaining: Concise and safe
const city2 = user?.address?.city;
console.log(city2); // 'New York'
// Handling non-existent properties
const country = user?.address?.country;
console.log(country); // undefined
// Array optional chaining
const users = [
{ name: 'Alice', posts: [{ title: 'Post 1' }] },
{ name: 'Bob' }
];
const firstPostTitle = users[0]?.posts?.[0]?.title;
console.log(firstPostTitle); // 'Post 1'
const secondPostTitle = users[1]?.posts?.[0]?.title;
console.log(secondPostTitle); // undefined
// Function call optional chaining
const userWithMethod = {
name: 'Alice',
greet: function() {
return 'Hello!';
}
};
const greeting = userWithMethod.greet?.();
console.log(greeting); // 'Hello!'
const noGreeting = userWithMethod.sayGoodbye?.();
console.log(noGreeting); // undefined
// Practical application: API response handling
function getUserCity(user) {
return user?.profile?.address?.city ?? 'Unknown';
}
console.log(getUserCity({ profile: { address: { city: 'Beijing' } } })); // 'Beijing'
console.log(getUserCity({ profile: {} })); // 'Unknown'
console.log(getUserCity(null)); // 'Unknown'
2. Nullish Coalescing Operator (??)
// Basic usage: Use default value only when null or undefined
const name = null ?? 'Guest';
console.log(name); // 'Guest'
const age = undefined ?? 18;
console.log(age); // 18
const active = false ?? true;
console.log(active); // false (false is not null or undefined)
const count = 0 ?? 10;
console.log(count); // 0 (0 is not null or undefined)
// Comparison with || operator
const value1 = 0 || 10; // 10 (0 is falsy)
const value2 = 0 ?? 10; // 0 (0 is not null/undefined)
const value3 = '' || 'default'; // 'default' (empty string is falsy)
const value4 = '' ?? 'default'; // '' (empty string is not null/undefined)
// Practical application: Configuration defaults
const config = {
timeout: null,
retries: undefined,
enabled: false,
port: 0
};
const finalConfig = {
timeout: config.timeout ?? 5000, // 5000
retries: config.retries ?? 3, // 3
enabled: config.enabled ?? true, // false (keeps original value)
port: config.port ?? 3000 // 0 (keeps original value)
};
// Function parameter defaults
function createUser(options = {}) {
return {
name: options.name ?? 'Guest',
age: options.age ?? 0,
email: options.email ?? null,
active: options.active ?? true
};
}
const user1 = createUser({ name: 'Alice', age: 0, active: false });
console.log(user1);
// { name: 'Alice', age: 0, email: null, active: false }
// Nested usage
const data = {
user: {
profile: null
}
};
const bio = data.user?.profile?.bio ?? 'No bio available';
console.log(bio); // 'No bio available'
3. Dynamic import()
// Basic dynamic import
async function loadModule() {
const module = await import('./math.js');
console.log(module.add(2, 3)); // 5
}
// Conditional import
async function loadFeature(featureName) {
if (featureName === 'charts') {
const charts = await import('./charts.js');
charts.init();
} else if (featureName === 'maps') {
const maps = await import('./maps.js');
maps.init();
}
}
// Lazy loading based on user interaction
document.getElementById('loadCharts').addEventListener('click', async () => {
const charts = await import('./charts.js');
charts.renderChart();
});
// Route-level code splitting
const routes = {
home: () => import('./pages/home.js'),
about: () => import('./pages/about.js'),
contact: () => import('./pages/contact.js')
};
async function loadPage(pageName) {
try {
const pageModule = await routes[pageName]();
pageModule.default.render();
} catch (error) {
console.error(`Failed to load ${pageName}:`, error);
}
}
// Dynamic import of multiple modules
async function loadUtils() {
const [dateUtils, stringUtils, mathUtils] = await Promise.all([
import('./utils/date.js'),
import('./utils/string.js'),
import('./utils/math.js')
]);
return { dateUtils, stringUtils, mathUtils };
}4. BigInt
// Creating BigInt
const bigInt1 = 123n; // Literal way
const bigInt2 = BigInt(123); // Constructor way
const bigInt3 = BigInt('123456789012345678901234567890');
// Basic operations
const a = 123n;
const b = 456n;
console.log(a + b); // 579n
console.log(a * b); // 56088n
console.log(a / b); // 0n (integer division)
console.log(a % b); // 123n
// Comparison with regular numbers
console.log(123n === 123); // false (different types)
console.log(123n == 123); // true (equal values)
// Large number calculations
const hugeNumber = BigInt('9007199254740991'); // Exceeds Number.MAX_SAFE_INTEGER
const result = hugeNumber * 2n;
console.log(result); // 18014398509481982n
// Practical application: Precise calculations
function preciseCalculation(a, b) {
return BigInt(a) * BigInt(b);
}
const result1 = preciseCalculation('12345678901234567890', '98765432109876543210');
console.log(result1.toString()); // '1219326311370217952237463801111263526900'
// JSON serialization support (requires custom handling)
const data = { bigNumber: 123n };
const json = JSON.stringify(data, (key, value) =>
typeof value === 'bigint' ? value.toString() + 'n' : value
);
console.log(json); // '{"bigNumber":"123n"}'
5. Promise.allSettled()
// Basic usage
const promises = [
Promise.resolve(1),
Promise.reject('Error 1'),
Promise.resolve(3),
Promise.reject('Error 2')
];
Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index}: ${result.value}`);
} else {
console.log(`Promise ${index}: ${result.reason}`);
}
});
});
// Comparison with Promise.all
async function comparePromiseMethods() {
const promises = [
Promise.resolve('Success 1'),
Promise.reject('Error'),
Promise.resolve('Success 2')
];
try {
// Promise.all - fails if any fails
const allResults = await Promise.all(promises);
console.log('All succeeded:', allResults);
} catch (error) {
console.log('Promise.all failed:', error);
}
// Promise.allSettled - waits for all to complete
const settledResults = await Promise.allSettled(promises);
console.log('All settled:', settledResults);
}
// Practical application: Batch operations
async function uploadFiles(files) {
const uploadPromises = files.map(file =>
uploadFile(file).catch(error => ({ error, file }))
);
const results = await Promise.allSettled(uploadPromises);
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return { successful, failed };
}
// Data fetching: Return successful data even if some fail
async function fetchMultipleAPIs() {
const apis = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments'
];
const results = await Promise.allSettled(
apis.map(url => fetch(url).then(r => r.json()))
);
return results.reduce((acc, result, index) => {
const apiName = apis[index].split('/').pop();
acc[apiName] = result.status === 'fulfilled' ? result.value : null;
return acc;
}, {});
}🎨 ES12 (2021): Simplified Syntax and Readability Improvements
ES12 introduced many syntax sugars that make code more concise and readable.
1. Logical Assignment Operators
// Logical AND assignment (&&=)
let user = { name: 'Alice', age: 30 };
user.age &&= user.age > 18 ? 'adult' : 'minor';
console.log(user.age); // 'adult'
// Equivalent to
// if (user.age) {
// user.age = user.age > 18 ? 'adult' : 'minor';
// }
// Logical OR assignment (||=)
let settings = { theme: 'dark' };
settings.language ||= 'en';
console.log(settings.language); // 'en'
// Equivalent to
// settings.language = settings.language || 'en';
// Nullish coalescing assignment (??=)
let config = { timeout: null };
config.timeout ??= 5000;
console.log(config.timeout); // 5000
// Equivalent to
// if (config.timeout === null || config.timeout === undefined) {
// config.timeout = 5000;
// }
// Practical application: Configuration updates
function updateConfig(currentConfig, newConfig) {
// Only update non-null values
newConfig.theme &&= (currentConfig.theme = newConfig.theme);
newConfig.language ??= (currentConfig.language = newConfig.language);
newConfig.timeout ||= (currentConfig.timeout = newConfig.timeout);
return currentConfig;
}
const current = { theme: 'light', language: null, timeout: 0 };
const updates = { theme: 'dark', language: 'zh', timeout: null };
const updated = updateConfig(current, updates);
console.log(updated);
// { theme: 'dark', language: 'zh', timeout: 0 }
// Conditional object property updates
const user = { name: 'Alice', email: null, age: 25 };
user.email ??= '[email protected]'; // Set default email
user.age &&= user.age >= 18 ? 'adult' : 'minor'; // Set status based on age
console.log(user);
// { name: 'Alice', email: '[email protected]', age: 'adult' }
2. Numeric Separators
// Basic usage: Use underscores to separate numbers
const billion = 1_000_000_000;
const million = 1_000_000;
const thousand = 1_000;
console.log(billion); // 1000000000
console.log(million); // 1000000
// Decimal part
const pi = 3.141_592_653;
console.log(pi); // 3.141592653
// Binary, octal, hexadecimal
const binary = 0b1010_1011_1100_1101;
const octal = 0o12_34_56;
const hex = 0xAB_CD_EF;
console.log(binary); // 43981
console.log(octal); // 34230
console.log(hex); // 11259375
// Practical application: Improve readability
const price = 1_299_99; // $1,299.99
const phoneNumber = 138_0013_8000; // Phone number
const macAddress = 0xAA_BB_CC_DD_EE_FF; // MAC address
// Scientific notation
const largeNumber = 1.234e5_678; // 123400000
console.log(largeNumber);
// Notes: Cannot use consecutive separators
// const invalid = 123__456; // SyntaxError
// const invalid2 = 123_; // SyntaxError
// const invalid3 = _123; // SyntaxError
// Usage in calculations
const calculation = (1_000_000 * 2) + (500_000 / 10);
console.log(calculation); // 2050000
3. String.prototype.replaceAll()
// Basic usage
const message = 'Hello world, hello universe, hello galaxy';
const replaced = message.replaceAll('hello', 'hi');
console.log(replaced); // 'Hi world, hi universe, hi galaxy'
// Case sensitive
const caseSensitive = 'Hello hello HELLO';
console.log(caseSensitive.replaceAll('hello', 'hi')); // 'Hello hi HELLO'
// Using regular expressions (needs global flag)
const regexReplaced = caseSensitive.replaceAll(/hello/gi, 'hi');
console.log(regexReplaced); // 'hi hi hi'
// Comparison with replace()
const text = 'apple, banana, apple, orange';
console.log(text.replace('apple', 'grape')); // 'grape, banana, apple, orange'
console.log(text.replaceAll('apple', 'grape')); // 'grape, banana, grape, orange'
// Practical application: Template variable replacement
function renderTemplate(template, data) {
let result = template;
for (const [key, value] of Object.entries(data)) {
const placeholder = `{{${key}}}`;
result = result.replaceAll(placeholder, value);
}
return result;
}
const template = 'Hello {{name}}, you have {{count}} new messages. Welcome to {{platform}}!';
const data = { name: 'Alice', count: 5, platform: 'OurApp' };
const rendered = renderTemplate(template, data);
console.log(rendered);
// 'Hello Alice, you have 5 new messages. Welcome to OurApp!'
// Code cleanup
function cleanCode(code) {
return code
.replaceAll(/\t/g, ' ') // Tabs to spaces
.replaceAll(/\r\n/g, '\n') // Windows newlines to Unix
.replaceAll(/ +/g, ' ') // Multiple spaces to single
.trim();
}
const messyCode = 'function test() {\t\treturn "hello";\r\n\r\n}';
console.log(cleanCode(messyCode));
// 'function test() { return "hello"; }'
4. Promise.any()
// Basic usage: Return first successful Promise
const promises = [
Promise.reject('Error 1'),
Promise.resolve('Success 1'),
Promise.reject('Error 2'),
Promise.resolve('Success 2')
];
Promise.any(promises).then(result => {
console.log(result); // 'Success 1' (first successful)
}).catch(error => {
console.error(error);
});
// When all Promises fail
const failedPromises = [
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.reject('Error 3')
];
Promise.any(failedPromises).catch(error => {
console.log(error); // AggregateError: All promises were rejected
console.log(error.errors); // ['Error 1', 'Error 2', 'Error 3']
});
// Practical application: Multiple API sources
async function fetchFromMultipleSources() {
const sources = [
fetch('https://api1.example.com/data').then(r => r.json()),
fetch('https://api2.example.com/data').then(r => r.json()),
fetch('https://api3.example.com/data').then(r => r.json())
];
try {
const data = await Promise.any(sources);
return data;
} catch (error) {
throw new Error('All data sources failed');
}
}
// Image loading: Use first successfully loaded image
async function loadFirstAvailableImage(urls) {
const imagePromises = urls.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = reject;
img.src = url;
});
});
try {
const firstLoadedUrl = await Promise.any(imagePromises);
return firstLoadedUrl;
} catch (error) {
throw new Error('No images could be loaded');
}
}
// Comparison with other Promise methods
const promises2 = [
Promise.resolve('A'),
Promise.resolve('B'),
Promise.resolve('C')
];
// Promise.any: First success
Promise.any(promises2).then(x => console.log('any:', x)); // 'A'
// Promise.race: First completion (success or failure)
Promise.race(promises2).then(x => console.log('race:', x)); // 'A'
// Promise.all: All success
Promise.all(promises2).then(x => console.log('all:', x)); // ['A', 'B', 'C']
🔒 ES13 (2022): Private Properties and Top-level await
ES13 brought important improvements in object-oriented programming and module systems.
1. Private Fields and Private Methods
class BankAccount {
// Private fields (start with #)
#balance = 0;
#accountNumber;
#transactions = [];
constructor(accountNumber, initialBalance = 0) {
this.#accountNumber = accountNumber;
this.#balance = initialBalance;
}
// Public methods
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#addTransaction('deposit', amount);
return true;
}
return false;
}
withdraw(amount) {
if (amount > 0 && this.#balance >= amount) {
this.#balance -= amount;
this.#addTransaction('withdraw', amount);
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
// Private method
#addTransaction(type, amount) {
this.#transactions.push({
type,
amount,
timestamp: new Date(),
balance: this.#balance
});
}
#validateTransaction(amount) {
return amount > 0 && Number.isFinite(amount);
}
// Private getter
get #transactionCount() {
return this.#transactions.length;
}
printStatement() {
console.log(`Account: ${this.#accountNumber}`);
console.log(`Balance: $${this.#balance}`);
console.log(`Transactions: ${this.#transactionCount}`);
this.#transactions.forEach(t => {
console.log(`${t.type}: $${t.amount} at ${t.timestamp}`);
});
}
}
// Usage example
const account = new BankAccount('12345', 1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 1300
account.printStatement();
// Private fields cannot be accessed from outside
// console.log(account.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class
// console.log(account.#accountNumber); // SyntaxError
// Private fields in inheritance
class SavingsAccount extends BankAccount {
#interestRate;
constructor(accountNumber, initialBalance, interestRate) {
super(accountNumber, initialBalance);
this.#interestRate = interestRate;
}
applyInterest() {
const interest = this.getBalance() * this.#interestRate;
// this.#balance += interest; // Error: Cannot access parent class private field
this.deposit(interest); // Access through public method
}
}
// Static private fields
class Counter {
static #instanceCount = 0;
static #instances = [];
constructor() {
Counter.#instanceCount++;
Counter.#instances.push(this);
}
static getInstanceCount() {
return Counter.#instanceCount;
}
static getAllInstances() {
return [...Counter.#instances];
}
}
const c1 = new Counter();
const c2 = new Counter();
console.log(Counter.getInstanceCount()); // 2
2. Top-level await
// Use await directly at module top level
// config.js
const response = await fetch('/api/config');
const config = await response.json();
export const apiUrl = config.apiUrl;
export const timeout = config.timeout;
// When using this module, it waits for config to load
// main.js
import { apiUrl, timeout } from './config.js';
console.log('API URL:', apiUrl); // Config already loaded
console.log('Timeout:', timeout);
// Practical application: Database connection initialization
// database.js
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db('myapp');
export const users = db.collection('users');
export const posts = db.collection('posts');
// Dynamic module loading
// dynamic-module.js
const moduleData = await import('./data.json');
export const data = moduleData.default;
// Conditional module import
// conditional-import.js
if (process.env.NODE_ENV === 'development') {
const devTools = await import('./dev-tools.js');
devTools.init();
}
export const isDevelopment = process.env.NODE_ENV === 'development';
// Async initialization pattern
// app.js
class App {
static async initialize() {
const app = new App();
await app.loadConfig();
await app.connectDatabase();
await app.setupRoutes();
return app;
}
async loadConfig() {
const response = await fetch('/config.json');
this.config = await response.json();
}
async connectDatabase() {
// Database connection logic
}
async setupRoutes() {
// Route setup logic
}
}
export const app = await App.initialize();
// Error handling
// In top-level await, errors prevent module loading
// error-handling.js
let config;
try {
const response = await fetch('/config.json');
config = await response.json();
} catch (error) {
console.error('Failed to load config:', error);
config = { apiUrl: 'http://localhost:3000', timeout: 5000 };
}
export default config;3. Class Static Initialization Blocks
class DataProcessor {
static data = [];
static config = {};
// Static initialization block
static {
console.log('Initializing DataProcessor...');
// Complex static initialization logic
try {
const response = fetchSync('/api/config');
this.config = response.json();
} catch (error) {
this.config = { timeout: 5000, retries: 3 };
}
// Initialize data
this.data = this.loadInitialData();
}
static loadInitialData() {
// Simulate data loading
return [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
];
}
static {
console.log('DataProcessor initialized with', this.data.length, 'items');
}
}
// Multiple static blocks
class ComplexClass {
static property1;
static property2;
static {
console.log('First static block');
this.property1 = 'value1';
}
static {
console.log('Second static block');
this.property2 = 'value2';
}
static {
// Can access previously set properties
console.log('Property1:', this.property1);
console.log('Property2:', this.property2);
}
}
// Async static initialization (requires top-level await)
// async-static-init.js
class AsyncInitializer {
static data;
static initialized = false;
static {
// Note: Static blocks themselves cannot be async
// But can use top-level await for async initialization
this.init();
}
static async init() {
try {
const response = await fetch('/api/data');
this.data = await response.json();
this.initialized = true;
} catch (error) {
console.error('Initialization failed:', error);
this.data = [];
this.initialized = false;
}
}
}
// Wait for initialization to complete
await AsyncInitializer.init();4. Error Cause
// Basic usage: Add cause to errors
function processData(data) {
if (!data) {
throw new Error('Data is required', { cause: 'input_validation' });
}
try {
return JSON.parse(data);
} catch (parseError) {
throw new Error('Invalid JSON format', {
cause: parseError
});
}
}
try {
processData('invalid json');
} catch (error) {
console.log(error.message); // 'Invalid JSON format'
console.log(error.cause); // Original JSON parsing error
}
// Practical application: API error handling
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`, {
cause: {
status: response.status,
statusText: response.statusText,
url: response.url
}
});
}
return await response.json();
} catch (error) {
if (error.cause) {
// Wrap existing error
throw new Error(`Failed to fetch user ${userId}`, {
cause: error
});
}
throw error;
}
}
// Error chain tracing
function validateUser(user) {
if (!user.email) {
throw new Error('Email is required', { cause: 'validation_error' });
}
if (!user.age) {
throw new Error('Age is required', { cause: 'validation_error' });
}
if (user.age < 18) {
throw new Error('User must be 18 or older', { cause: 'age_validation' });
}
}
try {
validateUser({ name: 'Alice', age: 16 });
} catch (error) {
console.log('Error:', error.message);
console.log('Cause:', error.cause);
// Error chain
let currentError = error;
while (currentError) {
console.log('->', currentError.message);
currentError = currentError.cause;
}
}
// Custom error classes
class ApplicationError extends Error {
constructor(message, code, cause) {
super(message, { cause });
this.name = 'ApplicationError';
this.code = code;
}
}
class DatabaseError extends ApplicationError {
constructor(operation, cause) {
super(`Database operation failed: ${operation}`, 'DATABASE_ERROR', cause);
this.name = 'DatabaseError';
this.operation = operation;
}
}
// Using custom errors
try {
// Simulate database error
throw new Error('Connection timeout');
} catch (dbError) {
throw new DatabaseError('user_lookup', dbError);
}🔧 ES14 (2023): Array Search and Script Improvements
ES14 introduced some practical array methods and script execution improvements.
1. Array.prototype.findLast() and findLastIndex()
// findLast() - Find matching element from back to front
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const lastEven = numbers.findLast(num => num % 2 === 0);
console.log(lastEven); // 4 (last even number)
const lastGreaterThanThree = numbers.findLast(num => num > 3);
console.log(lastGreaterThanThree); // 4 (last number greater than 3)
// findLastIndex() - Find index of matching element from back
const lastEvenIndex = numbers.findLastIndex(num => num % 2 === 0);
console.log(lastEvenIndex); // 5 (element at index 5 is last even)
const lastGreaterThanThreeIndex = numbers.findLastIndex(num => num > 3);
console.log(lastGreaterThanThreeIndex); // 5
// Comparison with find() and findIndex()
const firstEven = numbers.find(num => num % 2 === 0);
const firstEvenIndex = numbers.findIndex(num => num % 2 === 0);
console.log('First even:', firstEven, 'at index', firstEvenIndex); // 2 at 1
console.log('Last even:', lastEven, 'at index', lastEvenIndex); // 4 at 5
// Practical application: Find latest matching item
const logs = [
{ timestamp: '2023-01-01T10:00:00', level: 'info', message: 'App started' },
{ timestamp: '2023-01-01T10:05:00', level: 'warn', message: 'Low memory' },
{ timestamp: '2023-01-01T10:10:00', level: 'error', message: 'Database connection failed' },
{ timestamp: '2023-01-01T10:15:00', level: 'info', message: 'Retrying connection' },
{ timestamp: '2023-01-01T10:20:00', level: 'error', message: 'Connection failed again' }
];
// Find last error log
const lastError = logs.findLast(log => log.level === 'error');
console.log(lastError);
// { timestamp: '2023-01-01T10:20:00', level: 'error', message: 'Connection failed again' }
// Find first error after last warning
const lastWarnIndex = logs.findLastIndex(log => log.level === 'warn');
const errorAfterLastWarn = logs.slice(lastWarnIndex + 1).find(log => log.level === 'error');
console.log(errorAfterLastWarn);
// { timestamp: '2023-01-01T10:20:00', level: 'error', message: 'Connection failed again' }
// History management
const history = [
{ action: 'add', item: 'A', timestamp: 1 },
{ action: 'remove', item: 'B', timestamp: 2 },
{ action: 'add', item: 'C', timestamp: 3 },
{ action: 'remove', item: 'D', timestamp: 4 },
{ action: 'add', item: 'E', timestamp: 5 }
];
// Find last add operation
const lastAdd = history.findLast(entry => entry.action === 'add');
console.log(lastAdd); // { action: 'add', item: 'E', timestamp: 5 }
// Find last remove operation
const lastRemove = history.findLast(entry => entry.action === 'remove');
console.log(lastRemove); // { action: 'remove', item: 'D', timestamp: 4 }
2. Hashbang Syntax Support
// Use Hashbang at the beginning of JavaScript file
#!/usr/bin/env node
// my-script.js
console.log('Hello from Node.js script!');
// Can be executed directly on Unix systems
// ./my-script.js
// Script with parameters
#!/usr/bin/env node
const args = process.argv.slice(2);
console.log('Arguments:', args);
if (args.includes('--help')) {
console.log('Usage: ./my-script.js [options]');
process.exit(0);
}
// Practical application: Command line tools
#!/usr/bin/env node
import { readFile, writeFile } from 'fs/promises';
import { resolve } from 'path';
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: ./file-processor.js <input> <output>');
process.exit(1);
}
const [inputFile, outputFile] = args;
async function processFile() {
try {
const content = await readFile(resolve(inputFile), 'utf8');
const processed = content.toUpperCase();
await writeFile(resolve(outputFile), processed);
console.log(`File processed: ${inputFile} -> ${outputFile}`);
} catch (error) {
console.error('Error processing file:', error.message);
process.exit(1);
}
}
processFile();
// Configure in package.json
{
"bin": {
"my-tool": "./bin/my-tool.js"
}
}
// ./bin/my-tool.js
#!/usr/bin/env node
import cli from '../src/cli.js';
cli(process.argv);3. WeakMap Support for Symbol Keys
// ES14 allows using Symbol as WeakMap keys
const weakMap = new WeakMap();
const symbol1 = Symbol('description1');
const symbol2 = Symbol('description2');
const obj1 = { name: 'Object 1' };
const obj2 = { name: 'Object 2' };
// Use Symbol as key
weakMap.set(symbol1, obj1);
weakMap.set(symbol2, obj2);
console.log(weakMap.get(symbol1)); // { name: 'Object 1' }
console.log(weakMap.get(symbol2)); // { name: 'Object 2' }
// Check existence
console.log(weakMap.has(symbol1)); // true
console.log(weakMap.has(Symbol('description1'))); // false (different Symbol)
// Delete
weakMap.delete(symbol1);
console.log(weakMap.has(symbol1)); // false
// Practical application: Private data storage
class PrivateData {
constructor() {
this.#privateData = new WeakMap();
}
#privateData;
setPrivateData(obj, data) {
const key = Symbol.for(obj.constructor.name);
this.#privateData.set(key, data);
}
getPrivateData(obj) {
const key = Symbol.for(obj.constructor.name);
return this.#privateData.get(key);
}
}
// Metadata management
const metadata = new WeakMap();
function addMetadata(target, key, value) {
const symbolKey = Symbol(key);
metadata.set(symbolKey, value);
metadata.set(target, symbolKey);
}
function getMetadata(target, key) {
const symbolKey = metadata.get(target);
return symbolKey && metadata.get(symbolKey);
}
// Usage example
const user = { name: 'Alice' };
addMetadata(user, 'permissions', ['read', 'write']);
console.log(getMetadata(user, 'permissions')); // ['read', 'write']
🚀 ES15 (2024): Modern Regex and Async Arrays
ES15 continues to improve JavaScript functionality, especially in regular expressions and async operations.
1. RegExp v Mode (Unicode Property Escapes)
// Use v flag to enable Unicode property escapes
const regex = /\p{Script=Greek}/v;
console.log(regex.test('α')); // true (Greek letter)
console.log(regex.test('a')); // false (Latin letter)
// String properties
const letterRegex = /\p{L}/v; // Any letter
console.log(letterRegex.test('A')); // true
console.log(letterRegex.test('α')); // true
console.log(letterRegex.test('1')); // false
const numberRegex = /\p{N}/v; // Any number
console.log(numberRegex.test('1')); // true
console.log(numberRegex.test('١')); // true (Arabic numeral)
console.log(numberRegex.test('一')); // false (Chinese number is not Unicode number)
// Script properties
const chineseRegex = /\p{Script=Han}/v; // Chinese characters
console.log(chineseRegex.test('中')); // true
console.log(chineseRegex.test('国')); // true
console.log(chineseRegex.test('a')); // false
const emojiRegex = /\p{Emoji}/v; // Emoji symbols
console.log(emojiRegex.test('😀')); // true
console.log(emojiRegex.test('👍')); // true
console.log(emojiRegex.test('a')); // false
// Combined properties
const chineseLetterRegex = /\p{Script=Han}&\p{L}/v; // Chinese letters
console.log(chineseLetterRegex.test('中')); // true
// Practical application: Internationalized text processing
function detectLanguage(text) {
const scripts = {
Latin: /\p{Script=Latin}/v,
Greek: /\p{Script=Greek}/v,
Cyrillic: /\p{Script=Cyrillic}/v,
Han: /\p{Script=Han}/v,
Arabic: /\p{Script=Arabic}/v
};
for (const [script, regex] of Object.entries(scripts)) {
if (regex.test(text)) {
return script;
}
}
return 'Unknown';
}
console.log(detectLanguage('Hello')); // Latin
console.log(detectLanguage('你好')); // Han
console.log(detectLanguage('Привет')); // Cyrillic
// Password strength check
function checkPasswordStrength(password) {
const hasLetter = /\p{L}/v.test(password);
const hasNumber = /\p{N}/v.test(password);
const hasEmoji = /\p{Emoji}/v.test(password);
const hasHan = /\p{Script=Han}/v.test(password);
return {
hasLetter,
hasNumber,
hasEmoji,
hasHan,
isStrong: hasLetter && hasNumber && password.length >= 8
};
}
console.log(checkPasswordStrength('Password123'));
// { hasLetter: true, hasNumber: true, hasEmoji: false, hasHan: false, isStrong: true }
2. Array.fromAsync()
// Basic usage: Create array from async iterable
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
const array = await Array.fromAsync(asyncGenerator());
console.log(array); // [1, 2, 3]
// Create array from async Map
const asyncMap = new Map([
[Promise.resolve('key1'), Promise.resolve('value1')],
[Promise.resolve('key2'), Promise.resolve('value2')]
]);
const mapArray = await Array.fromAsync(asyncMap.entries());
console.log(mapArray); // [['key1', 'value1'], ['key2', 'value2']]
// Practical application: Async data processing
async function fetchUsers(ids) {
const userPromises = ids.map(id =>
fetch(`/api/users/${id}`).then(r => r.json())
);
const users = await Array.fromAsync(userPromises);
return users;
}
const users = await fetchUsers([1, 2, 3]);
console.log(users); // [{ id: 1, ... }, { id: 2, ... }, { id: 3, ... }]
// Paginated data collection
async function* fetchAllPages(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
async function getAllItems(url) {
const allPages = await Array.fromAsync(fetchAllPages(url));
return allPages.flat();
}
// File processing
async function* readFiles(filePaths) {
for (const filePath of filePaths) {
const content = await readFile(filePath, 'utf8');
yield content;
}
}
async function concatenateFiles(filePaths) {
const contents = await Array.fromAsync(readFiles(filePaths));
return contents.join('\n');
}
// Async filtering and mapping
async function processAsyncData() {
const numbers = [1, 2, 3, 4, 5];
// Async mapping
const doubled = await Array.fromAsync(
numbers.map(async n => {
await new Promise(resolve => setTimeout(resolve, 100));
return n * 2;
})
);
console.log(doubled); // [2, 4, 6, 8, 10]
}
// Comparison with other async methods
async function compareAsyncMethods() {
const asyncIterable = (async function*() {
yield 1;
yield 2;
yield 3;
})();
// Array.fromAsync
const array1 = await Array.fromAsync(asyncIterable);
console.log('Array.fromAsync:', array1);
// Manual collection
const array2 = [];
for await (const item of asyncIterable) {
array2.push(item);
}
console.log('Manual collection:', array2);
// Promise.all (need to convert to array first)
const promises = [];
for await (const item of asyncIterable) {
promises.push(Promise.resolve(item));
}
const array3 = await Promise.all(promises);
console.log('Promise.all:', array3);
}3. Object.groupBy() and Map.groupBy()
// Object.groupBy() - Group objects by condition
const people = [
{ name: 'Alice', age: 25, department: 'Engineering' },
{ name: 'Bob', age: 30, department: 'Marketing' },
{ name: 'Charlie', age: 35, department: 'Engineering' },
{ name: 'Diana', age: 28, department: 'Marketing' }
];
const byDepartment = Object.groupBy(people, person => person.department);
console.log(byDepartment);
// {
// Engineering: [
// { name: 'Alice', age: 25, department: 'Engineering' },
// { name: 'Charlie', age: 35, department: 'Engineering' }
// ],
// Marketing: [
// { name: 'Bob', age: 30, department: 'Marketing' },
// { name: 'Diana', age: 28, department: 'Marketing' }
// ]
// }
// Group by age
const byAgeGroup = Object.groupBy(people, person => {
if (person.age < 30) return 'Young';
return 'Senior';
});
console.log(byAgeGroup);
// {
// Young: [
// { name: 'Alice', age: 25, department: 'Engineering' },
// { name: 'Diana', age: 28, department: 'Marketing' }
// ],
// Senior: [
// { name: 'Bob', age: 30, department: 'Marketing' },
// { name: 'Charlie', age: 35, department: 'Engineering' }
// ]
// }
// Map.groupBy() - Returns Map instead of object
const departmentMap = Map.groupBy(people, person => person.department);
console.log(departmentMap.get('Engineering'));
// [
// { name: 'Alice', age: 25, department: 'Engineering' },
// { name: 'Charlie', age: 35, department: 'Engineering' }
// ]
// Practical application: Data processing
const products = [
{ id: 1, name: 'Laptop', price: 1000, category: 'Electronics' },
{ id: 2, name: 'Book', price: 20, category: 'Education' },
{ id: 3, name: 'Phone', price: 500, category: 'Electronics' },
{ id: 4, name: 'Course', price: 100, category: 'Education' }
];
// Group by category and calculate statistics
const categoryStats = Object.groupBy(products, product => product.category);
Object.keys(categoryStats).forEach(category => {
const items = categoryStats[category];
const totalValue = items.reduce((sum, item) => sum + item.price, 0);
console.log(`${category}: ${items.length} items, total value: $${totalValue}`);
});
// Complex grouping logic
const transactions = [
{ id: 1, amount: 100, type: 'income', date: '2023-01-01' },
{ id: 2, amount: -50, type: 'expense', date: '2023-01-02' },
{ id: 3, amount: 200, type: 'income', date: '2023-01-03' },
{ id: 4, amount: -75, type: 'expense', date: '2023-01-04' }
];
// Group by month and type
const monthlyTransactions = Object.groupBy(transactions, transaction => {
const month = transaction.date.substring(0, 7); // '2023-01'
return `${month}-${transaction.type}`;
});
console.log(monthlyTransactions);
// {
// '2023-01-income': [
// { id: 1, amount: 100, type: 'income', date: '2023-01-01' },
// { id: 3, amount: 200, type: 'income', date: '2023-01-03' }
// ],
// '2023-01-expense': [
// { id: 2, amount: -50, type: 'expense', date: '2023-01-02' },
// { id: 4, amount: -75, type: 'expense', date: '2023-01-04' }
// ]
// }
🛠️ Using and Detecting ES Versions in Projects
1. TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020", // Compilation target
"module": "ESNext", // Module system
"lib": ["ES2020", "DOM"], // Libraries to use
"moduleResolution": "node", // Module resolution
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}2. Babel Configuration
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1%', 'last 2 versions', 'not dead']
},
useBuiltIns: 'usage',
corejs: 3
}]
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator'
]
};3. Vite Configuration
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
target: 'es2020', // Build target
polyfillDynamicImport: true
},
define: {
__VERSION__: JSON.stringify(process.env.npm_package_version)
}
});4. Browser Compatibility Check
// Check ES feature support
function checkESSupport() {
const features = {
'Arrow Functions': () => { try { eval('() => {}'); return true; } catch { return false; } },
'Async/Await': () => { try { eval('async function f(){}'); return true; } catch { return false; } },
'Optional Chaining': () => { try { eval('const obj = {}; obj?.prop'); return true; } catch { return false; } },
'Nullish Coalescing': () => { try { eval('const a = null ?? "default"'); return true; } catch { return false; } },
'Private Fields': () => { try { eval('class C { #field = 1; }'); return true; } catch { return false; } }
};
const results = {};
for (const [name, test] of Object.entries(features)) {
results[name] = test();
}
return results;
}
console.log('ES Features Support:', checkESSupport());📚 Learning Suggestions and Roadmap
1. Foundation Stage: ES6 Core Features
Learning Focus:
let/constand block scope- Arrow functions and this binding
- Destructuring assignment and spread operator
- Template literals
- Promise basics
- Classes and modules
Practice Projects:
- Build a todo application
- Implement simple data visualization
- Create a personal blog system
2. Intermediate Stage: ES7-ES11 Practical Features
Learning Focus:
async/awaitasync programming- Optional chaining and nullish coalescing
- Dynamic imports and code splitting
- Enhanced array methods
- New object operation methods
Practice Projects:
- Develop an e-commerce shopping cart
- Implement a real-time chat application
- Build a data management dashboard
3. Advanced Stage: ES12-ES15 Cutting-edge Features
Learning Focus:
- Private fields and class enhancements
- Top-level await and module systems
- Advanced regular expression features
- Async array operations
- Performance optimization techniques
Practice Projects:
- Enterprise-level application architecture
- Micro-frontend systems
- Performance monitoring tools
4. Framework Integration Learning
React Ecosystem:
// Use modern ES syntax
const UserProfile = ({ user, onUpdate }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleUpdate = async (updates) => {
try {
setLoading(true);
const updatedUser = await updateUser(user.id, updates);
onUpdate?.(updatedUser);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="user-profile">
<h2>{user?.name ?? 'Unknown User'}</h2>
{/* Component content */}
</div>
);
};Vue Ecosystem:
// Composition API + ES features
import { ref, computed, onMounted } from 'vue';
export default {
props: {
userId: {
type: String,
required: true
}
},
setup(props) {
const user = ref(null);
const loading = ref(false);
const error = ref(null);
const displayName = computed(() =>
user.value?.name ?? 'Loading...'
);
const loadUser = async () => {
try {
loading.value = true;
const response = await fetch(`/api/users/${props.userId}`);
user.value = await response.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
onMounted(loadUser);
return {
user,
loading,
error,
displayName,
loadUser
};
}
};🎯 Best Practices
1. Code Style
// Use modern ES syntax
const api = {
// Use optional chaining and nullish coalescing
async getUser(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return data?.user ?? null;
},
// Use destructuring and default parameters
async updateUser(id, updates = {}) {
const { name, email, ...other } = updates;
const payload = {
...(name && { name }),
...(email && { email }),
...other
};
const response = await fetch(`/api/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response.json();
}
};
// Use private fields
class UserService {
#cache = new Map();
#baseUrl;
constructor(baseUrl) {
this.#baseUrl = baseUrl;
}
async getUser(id) {
// Check cache
if (this.#cache.has(id)) {
return this.#cache.get(id);
}
// Fetch data
const user = await this.#fetchUser(id);
// Cache result
this.#cache.set(id, user);
return user;
}
async #fetchUser(id) {
const response = await fetch(`${this.#baseUrl}/users/${id}`);
return response.json();
}
}2. Error Handling
// Use optional chaining and nullish coalescing for safe access
function safeGetUser(data) {
return {
name: data?.user?.profile?.name ?? 'Unknown',
email: data?.user?.contact?.email ?? 'No email',
age: data?.user?.profile?.age ?? 0
};
}
// Use Promise.allSettled for batch operations
async function fetchMultipleData(urls) {
const results = await Promise.allSettled(
urls.map(url => fetch(url).then(r => r.json()))
);
return results.map((result, index) => ({
url: urls[index],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : null,
error: result.status === 'rejected' ? result.reason : null
}));
}
// Use Error Cause for error chain tracing
class APIError extends Error {
constructor(message, statusCode, cause) {
super(message, { cause });
this.name = 'APIError';
this.statusCode = statusCode;
}
}
async function apiCall(endpoint) {
try {
const response = await fetch(endpoint);
if (!response.ok) {
throw new APIError(
`API call failed: ${response.statusText}`,
response.status,
{ endpoint, status: response.status }
);
}
return await response.json();
} catch (error) {
if (error instanceof APIError) {
throw error;
}
throw new APIError(
'Network error occurred',
0,
{ endpoint, originalError: error }
);
}
}3. Performance Optimization
// Use dynamic imports for code splitting
class LazyLoader {
static modules = new Map();
static async load(moduleName) {
if (this.modules.has(moduleName)) {
return this.modules.get(moduleName);
}
try {
const module = await import(`./modules/${moduleName}.js`);
this.modules.set(moduleName, module);
return module;
} catch (error) {
console.error(`Failed to load module ${moduleName}:`, error);
throw error;
}
}
}
// Use Array.fromAsync for async data processing
async function processLargeDataset(data) {
const chunks = await Array.fromAsync(
createAsyncChunks(data, 1000) // Process 1000 items at a time
);
const results = [];
for (const chunk of chunks) {
const processed = await processChunk(chunk);
results.push(...processed);
}
return results;
}
async function* createAsyncChunks(array, size) {
for (let i = 0; i < array.length; i += size) {
yield array.slice(i, i + size);
}
}📖 Related Resources
Official Documentation
Learning Tools
Compatibility Check
Conclusion
The continuous development of ECMAScript brings JavaScript developers increasingly powerful tools and more elegant syntax. From the revolutionary changes of ES6 to the refined improvements of ES15, each version enhances development efficiency and code quality.
Mastering modern JavaScript is not just about learning new syntax, but more importantly, understanding how these features solve real problems, how to improve code readability, maintainability, and performance. Through progressive learning and practice, you’ll be able to fully leverage these modern features to write more elegant and efficient JavaScript code.
Remember, learning is a continuous process. Stay updated on new features, actively participate in community discussions, and apply new technologies in actual projects - these are all key to becoming an excellent JavaScript developer. Start your modern JavaScript journey today!
WenHaoFree