ECMAScript新特性
本章将介绍ECMAScript 6及后续版本中的最新特性,包括模块化系统、异步编程、新的数据结构、装饰器等,帮助您掌握现代JavaScript的前沿技术。
16.1 模块化系统
ES6引入了原生的模块化系统,使代码组织和复用更加规范。
导出模块
// math.js - 命名导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// 默认导出
export default function multiply(a, b) {
return a * b;
}
// 或者单独声明默认导出
// export default (a, b) => a * b;
导入模块
// main.js - 导入模块
import multiply, { PI, add, subtract } from './math.js';
import { add as sum, subtract as diff } from './math.js';
import * as math from './math.js';
console.log(PI); // 3.14159
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6
console.log(multiply(3, 4)); // 12
console.log(sum(5, 3)); // 8
console.log(diff(10, 4)); // 6
console.log(math.PI); // 3.14159
console.log(math.add(5, 3)); // 8
动态导入
// 动态导入 (ES2020)
async function loadMathModule() {
try {
const math = await import('./math.js');
console.log(math.add(5, 3)); // 8
console.log(math.PI); // 3.14159
} catch (error) {
console.error('模块加载失败:', error);
}
}
// 条件导入
if (condition) {
import('./moduleA.js').then(module => {
module.doSomething();
});
} else {
import('./moduleB.js').then(module => {
module.doSomethingElse();
});
}
16.2 异步编程
现代JavaScript提供了多种异步编程方式。
Promise
// Promise基本用法
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url) {
resolve(`数据来自: ${url}`);
} else {
reject(new Error("URL不能为空"));
}
}, 1000);
});
}
// 使用Promise
fetchData("https://api.example.com")
.then(data => {
console.log(data);
return fetchData("https://api.example.com/users");
})
.then(userData => {
console.log(userData);
})
.catch(error => {
console.error("错误:", error.message);
})
.finally(() => {
console.log("请求完成");
});
async/await
// async/await语法
async function fetchUserData() {
try {
const data = await fetchData("https://api.example.com");
console.log(data);
const userData = await fetchData("https://api.example.com/users");
console.log(userData);
return userData;
} catch (error) {
console.error("错误:", error.message);
throw error;
}
}
// 并行执行
async function fetchMultipleData() {
try {
// 并行执行多个异步操作
const [data1, data2, data3] = await Promise.all([
fetchData("https://api.example.com/1"),
fetchData("https://api.example.com/2"),
fetchData("https://api.example.com/3")
]);
console.log(data1, data2, data3);
} catch (error) {
console.error("错误:", error.message);
}
}
// 串行执行
async function fetchSequentialData() {
try {
const data1 = await fetchData("https://api.example.com/1");
const data2 = await fetchData("https://api.example.com/2");
const data3 = await fetchData("https://api.example.com/3");
console.log(data1, data2, data3);
} catch (error) {
console.error("错误:", error.message);
}
}
Promise新方法
// Promise.allSettled (ES2020)
Promise.allSettled([
Promise.resolve(1),
Promise.reject("错误"),
Promise.resolve(3)
]).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} 成功:`, result.value);
} else {
console.log(`Promise ${index} 失败:`, result.reason);
}
});
});
// Promise.any (ES2021)
Promise.any([
Promise.reject("失败1"),
Promise.resolve("成功"),
Promise.reject("失败2")
]).then(result => {
console.log(result); // "成功"
}).catch(error => {
console.error("所有Promise都失败了:", error.errors);
});
16.3 新的数据结构
ES6及后续版本引入了新的数据结构。
Map和Set
// Map - 键值对集合
let userRoles = new Map([
["张三", "管理员"],
["李四", "用户"],
["王五", "访客"]
]);
userRoles.set("赵六", "用户");
console.log(userRoles.get("张三")); // "管理员"
console.log(userRoles.size); // 4
// 遍历Map
for (let [user, role] of userRoles) {
console.log(`${user}: ${role}`);
}
// Set - 唯一值集合
let tags = new Set(["JavaScript", "ES6", "前端", "JavaScript"]);
console.log(tags.size); // 3 (重复值被忽略)
console.log(tags.has("JavaScript")); // true
// Set操作
tags.add("React");
tags.delete("前端");
console.log([...tags]); // ["JavaScript", "ES6", "React"]
WeakMap和WeakSet
// WeakMap - 弱引用Map
let wm = new WeakMap();
let obj = {};
wm.set(obj, "私有数据");
console.log(wm.get(obj)); // "私有数据"
// 当obj没有其他引用时,WeakMap中的条目会被自动清除
obj = null;
// WeakSet - 弱引用Set
let ws = new WeakSet();
let obj1 = {};
let obj2 = {};
ws.add(obj1);
ws.add(obj2);
console.log(ws.has(obj1)); // true
// 当对象没有其他引用时,WeakSet中的条目会被自动清除
obj1 = null;
16.4 新的语法特性
现代JavaScript引入了许多新的语法特性。
可选链操作符 (?.) (ES2020)
// 可选链操作符
let user = {
name: "张三",
address: {
city: "北京"
}
};
// 安全访问嵌套属性
console.log(user?.address?.city); // "北京"
console.log(user?.address?.country); // undefined (不会报错)
console.log(user?.phone?.number); // undefined (不会报错)
// 可选链调用方法
let user2 = {
name: "李四",
greet: function() {
return `你好, ${this.name}`;
}
};
console.log(user2?.greet?.()); // "你好, 李四"
console.log(user2?.sayGoodbye?.()); // undefined (不会报错)
空值合并操作符 (??) (ES2020)
// 空值合并操作符
let username = null;
let defaultName = "匿名用户";
// 只有当左侧为null或undefined时才返回右侧值
let displayName = username ?? defaultName;
console.log(displayName); // "匿名用户"
let age = 0;
let defaultAge = 18;
// 与逻辑或操作符的区别
let displayAge1 = age || defaultAge; // 18 (0被视为falsy)
let displayAge2 = age ?? defaultAge; // 0 (0不是null或undefined)
console.log(displayAge1, displayAge2); // 18 0
逻辑赋值操作符 (ES2021)
// 逻辑赋值操作符
let user = {name: "张三"};
// 逻辑与赋值 (&&=)
user.id &&= generateId(); // 只有当user.id为truthy时才赋值
// 逻辑或赋值 (||=)
user.nickname ||= "匿名用户"; // 只有当user.nickname为falsy时才赋值
// 空值合并赋值 (??=)
user.avatar ??= "/default-avatar.png"; // 只有当user.avatar为null或undefined时才赋值
数字分隔符 (ES2021)
// 数字分隔符
let budget = 1_000_000_000; // 10亿
let bytes = 0x89_AB_CD_EF; // 十六进制
let hugeNumber = 1_000_000_000_000n; // BigInt
console.log(budget); // 1000000000
console.log(bytes); // 2309737967
console.log(hugeNumber); // 1000000000000n
16.5 字符串和正则表达式新特性
字符串和正则表达式也获得了新的特性。
字符串匹配索引 (ES2022)
// 字符串匹配索引
let text = "Hello, Hello, Hello";
let regex = /Hello/dg; // d标志启用indices属性
let match = regex.exec(text);
console.log(match.indices); // [[0, 5]]
match = regex.exec(text);
console.log(match.indices); // [[7, 12]]
at()方法 (ES2022)
// at()方法
let arr = [1, 2, 3, 4, 5];
let str = "Hello";
console.log(arr.at(0)); // 1 (等价于arr[0])
console.log(arr.at(-1)); // 5 (最后一个元素)
console.log(arr.at(-2)); // 4 (倒数第二个元素)
console.log(str.at(0)); // "H"
console.log(str.at(-1)); // "o"
16.6 数组新方法
数组也添加了新的方法。
findLast()和findLastIndex() (ES2023)
// findLast()和findLastIndex()
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 从后往前查找
let lastEven = numbers.findLast(n => n % 2 === 0);
console.log(lastEven); // 10
let lastEvenIndex = numbers.findLastIndex(n => n % 2 === 0);
console.log(lastEvenIndex); // 9
toReversed(), toSorted(), toSpliced() (ES2023)
// 不修改原数组的变异方法
let arr = [3, 1, 4, 1, 5, 9, 2, 6];
// 返回新数组,不修改原数组
let reversed = arr.toReversed();
let sorted = arr.toSorted();
let spliced = arr.toSpliced(2, 2, 'a', 'b');
console.log(arr); // [3, 1, 4, 1, 5, 9, 2, 6] (原数组不变)
console.log(reversed); // [6, 2, 9, 5, 1, 4, 1, 3]
console.log(sorted); // [1, 1, 2, 3, 4, 5, 6, 9]
console.log(spliced); // [3, 1, 'a', 'b', 5, 9, 2, 6]
16.7 装饰器 (Stage 3)
装饰器是JavaScript的一个提案特性,用于修改类的行为。
// 装饰器示例(需要转译器支持)
function readonly(target, propertyKey, descriptor) {
descriptor.writable = false;
return descriptor;
}
function log(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用 ${propertyKey} 方法,参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 返回:`, result);
return result;
};
return descriptor;
}
class Calculator {
@readonly
PI = 3.14159;
@log
add(a, b) {
return a + b;
}
@log
multiply(a, b) {
return a * b;
}
}
let calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.multiply(4, 6)); // 24
// calc.PI = 3.14; // 严格模式下会报错
16.8 顶层await (ES2022)
顶层await允许在模块顶层使用await,而不需要包装在async函数中。
// 顶层await (在模块中)
// data.js
const data = await fetch('/api/data').then(res => res.json());
export default data;
// config.js
const config = await import('./config.json', { assert: { type: 'json' } });
export default config;
// main.js
import data from './data.js';
import config from './config.js';
console.log(data);
console.log(config);
16.9 实际应用示例
现代API客户端
// 现代API客户端示例
class ApiClient {
#baseUrl;
#defaultHeaders;
constructor(baseUrl, defaultHeaders = {}) {
this.#baseUrl = baseUrl;
this.#defaultHeaders = {
'Content-Type': 'application/json',
...defaultHeaders
};
}
// 私有方法
async #request(endpoint, options = {}) {
const url = `${this.#baseUrl}${endpoint}`;
const config = {
headers: { ...this.#defaultHeaders, ...options.headers },
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
// 公共方法
async get(endpoint, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
return this.#request(url, { method: 'GET' });
}
async post(endpoint, data) {
return this.#request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
async put(endpoint, data) {
return this.#request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(endpoint) {
return this.#request(endpoint, { method: 'DELETE' });
}
}
// 使用示例
const api = new ApiClient('https://api.example.com');
// 获取用户数据
async function loadUserData() {
try {
const users = await api.get('/users', { page: 1, limit: 10 });
console.log('用户数据:', users);
// 创建新用户
const newUser = await api.post('/users', {
name: '张三',
email: 'zhangsan@example.com'
});
console.log('新用户:', newUser);
} catch (error) {
console.error('加载用户数据失败:', error);
}
}
数据处理管道
// 数据处理管道示例
class DataPipeline {
#steps = [];
// 添加处理步骤
addStep(transformer) {
this.#steps.push(transformer);
return this; // 支持链式调用
}
// 执行管道
async execute(data) {
let result = data;
for (const step of this.#steps) {
try {
// 支持同步和异步转换器
result = await step(result);
} catch (error) {
console.error('管道步骤执行失败:', error);
throw error;
}
}
return result;
}
// 静态工厂方法
static create() {
return new DataPipeline();
}
}
// 使用示例
const pipeline = DataPipeline.create()
.addStep(data => data.filter(item => item.active))
.addStep(async data => {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
return data.map(item => ({ ...item, processed: true }));
})
.addStep(data => data.sort((a, b) => a.name.localeCompare(b.name)));
// 执行管道
async function processData() {
const rawData = [
{ name: '张三', active: true },
{ name: '李四', active: false },
{ name: '王五', active: true }
];
try {
const result = await pipeline.execute(rawData);
console.log('处理结果:', result);
} catch (error) {
console.error('数据处理失败:', error);
}
}
提示:ECMAScript新特性让JavaScript变得更加强大和现代化。在实际开发中,应根据项目需求和浏览器兼容性要求选择合适的特性。对于需要支持旧版本浏览器的项目,可以使用Babel等工具进行转译。