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等工具进行转译。