JavaScript系列05-现代JavaScript新特性
JavaScript作为网络的核心语言之一,近年来发展迅速。从ES6(ECMAScript 2015)开始,JavaScript几乎每年都有新的语言特性加入,极大地改善了开发体验和代码质量。本文主要内容包括:
- ES6+关键特性:
- 解构赋值与扩展运算符:
- 模块化开发:
- Proxy与Reflect:
- 新的API与语法糖:
- 兼容性与Polyfill:
1、ES6+关键特性
ES6是JavaScript发展的一个重要里程碑,带来了大量革命性的改变,后续版本继续添加了许多实用特性。
箭头函数
箭头函数提供了更简洁的函数定义语法,并且自动绑定词法上下文的this
。
// 传统函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
// 箭头函数与this
const person = {
name: '张三',
sayHiTraditional: function() {
setTimeout(function() {
console.log(`你好,我是${this.name}`); // this不是person
}, 1000);
},
sayHiArrow: function() {
setTimeout(() => {
console.log(`你好,我是${this.name}`); // this绑定到person
}, 1000);
}
};
箭头函数的特点:
- 更简洁的语法
- 没有自己的
this
,继承外围作用域的this
- 没有
arguments
对象 - 不能用作构造函数
- 没有
prototype
属性
let与const
ES6引入了块级作用域的变量声明方式:
// var的问题:没有块级作用域
if (true) {
var x = 10;
}
console.log(x); // 10,x泄漏到外部作用域
// let的块级作用域
if (true) {
let y = 20;
}
// console.log(y); // ReferenceError: y未定义
// const声明常量,值不可重新赋值
const PI = 3.14159;
// PI = 3.14; // TypeError: 赋值给常量变量
// const声明的对象内容可以修改
const person = { name: '李四' };
person.name = '王五'; // 这是允许的
// person = {}; // 错误:不能重新赋值
let/const与var的主要区别:
- 块级作用域vs函数作用域
- 没有变量提升(实际上有"暂时性死区")
- 禁止重复声明
- 全局声明的变量不会成为window的属性
类语法
ES6引入了类语法,使面向对象编程更直观:
class Animal {
// 构造函数
constructor(name) {
this.name = name;
}
// 实例方法
speak() {
return `${this.name}发出声音`;
}
// 静态方法
static isAnimal(obj) {
return obj instanceof Animal;
}
// ES2022+:私有字段
#privateField = 'private';
getPrivate() {
return this.#privateField;
}
}
// 继承
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
return `${this.name}汪汪叫`;
}
// 获取器方法
get description() {
return `${this.breed}犬${this.name}`;
}
}
const dog = new Dog('旺财', '哈士奇');
console.log(dog.speak()); // "旺财汪汪叫"
console.log(dog.description); // "哈士奇犬旺财"
Promise及异步编程
Promise为异步操作提供了更优雅的处理方式:
// Promise基本用法
function fetchData(url) {
return new Promise((resolve, reject) => {
const success = Math.random() > 0.3;
setTimeout(() => {
if (success) {
resolve(`来自${url}的数据`);
} else {
reject(`获取${url}失败`);
}
}, 1000);
});
}
fetchData('api/users')
.then(data => {
console.log(data);
return fetchData('api/orders');
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('请求结束,无论成功或失败');
});
ES2017引入的async/await使异步代码更像同步代码:
async function fetchAllData() {
try {
const users = await fetchData('api/users');
console.log(users);
const orders = await fetchData('api/orders');
console.log(orders);
return { users, orders };
} catch (error) {
console.error('获取数据失败:', error);
} finally {
console.log('操作完成');
}
}
// 调用异步函数
fetchAllData().then(result => {
console.log('所有数据:', result);
});
可选链与空值合并
ES2020引入了两个非常实用的运算符:
// 可选链运算符 ?.
const user = {
profile: {
// address: {
// city: '北京'
// }
}
};
// 使用前:防御性编程
const city = user && user.profile && user.profile.address && user.profile.address.city || '未知城市';
// 使用后:简洁优雅
const city2 = user?.profile?.address?.city || '未知城市';
// 空值合并运算符 ??
// 与 || 不同,?? 只在值为 null 或 undefined 时生效
const count = 0;
console.log(count || 10); // 10,因为0是假值
console.log(count ?? 10); // 0,因为0不是null或undefined
2、解构赋值与扩展运算符
解构赋值和扩展运算符是现代JavaScript中最常用的语法特性之一。
数组解构
// 基本数组解构
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// 交换变量,无需临时变量
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// 忽略某些值
const [x, , z] = [1, 2, 3];
console.log(x, z); // 1 3
// 设置默认值
const [p = 0, q = 0] = [10];
console.log(p, q); // 10 0
对象解构
// 基本对象解构
const person = {
name: '张三',
age: 30,
job: '开发者',
address: {
city: '上海',
street: '南京路'
}
};
const { name, age, hobby = '阅读' } = person;
console.log(name, age, hobby); // "张三" 30 "阅读"
// 重命名
const { name: fullName, job: occupation } = person;
console.log(fullName, occupation); // "张三" "开发者"
// 嵌套解构
const { address: { city } } = person;
console.log(city); // "上海"
// 解构与函数参数
function printPerson({ name, age, job = '无业' }) {
console.log(`${name}, ${age}岁, 职业: ${job}`);
}
printPerson(person); // "张三, 30岁, 职业: 开发者"
扩展运算符
扩展运算符(...
)在数组、对象和函数参数中非常有用:
// 数组合并
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 数组克隆
const original = [1, 2, 3];
const copy = [...original];
original.push(4);
console.log(original); // [1, 2, 3, 4]
console.log(copy); // [1, 2, 3]
// 对象合并与克隆
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 }; // b将覆盖obj1的b
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 3, c: 4 }
// 对象克隆(浅拷贝)
const originalObj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...originalObj };
originalObj.a = 10;
originalObj.b.c = 20;
console.log(shallowCopy.a); // 1 (不受影响)
console.log(shallowCopy.b.c); // 20 (受影响,因为是浅拷贝)
实际应用解构与扩展
// React组件中的状态更新
const [state, setState] = useState({ count: 0, loaded: false });
setState(prevState => ({ ...prevState, count: prevState.count + 1 }));
// API响应处理
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// 只提取需要的字段并设置默认值
const {
name,
email,
roles = ['user'],
settings: { theme = 'light' } = {}
} = data;
return { name, email, roles, theme };
}
3、模块化开发
JavaScript模块化经历了从无到有的发展过程,ES6正式将模块系统引入语言标准。
模块系统的演进
ES模块基本语法
// 导出语法 (math.js)
// 命名导出
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;
}
// 导入语法 (app.js)
// 导入默认导出
import multiply from './math.js';
// 导入命名导出
import { add, subtract } from './math.js';
// 导入所有导出为一个对象
import * as mathUtils from './math.js';
// 重命名导入
import { add as sum, subtract as minus } from './math.js';
// 混合导入
import multiply, { add, subtract } from './math.js';
// 使用
console.log(add(5, 3)); // 8
console.log(multiply(4, 2)); // 8
console.log(mathUtils.subtract(10, 5)); // 5
动态导入
ES2020标准化了动态导入,支持按需加载模块:
// 静态导入(在顶层)
import { feature1 } from './features.js';
// 动态导入(按需)
button.addEventListener('click', async () => {
try {
// 只有用户点击按钮后才加载
const { feature2 } = await import('./features.js');
feature2();
} catch (error) {
console.error('模块加载失败:', error);
}
});
CommonJS与ES模块的区别
// CommonJS (Node.js)
const fs = require('fs');
const { join } = require('path');
module.exports = {
readFile: function(path) { /* ... */ },
writeFile: function(path, data) { /* ... */ }
};
// ES模块 (ES6+)
import fs from 'fs';
import { join } from 'path';
export function readFile(path) { /* ... */ }
export function writeFile(path, data) { /* ... */ }
主要区别:
- 加载时机:CommonJS是运行时加载,ES模块是静态加载
- 导入类型:CommonJS导入的是值的拷贝,ES模块导入的是值的引用
- this指向:CommonJS顶层this指向当前模块,ES模块顶层this是undefined
- 语法位置:CommonJS可以在条件语句中require,ES静态import必须在顶层
模块设计最佳实践
- 单一职责原则:每个模块只负责一个功能
- 显式导出API:明确声明公共API,减少副作用
- 避免循环依赖:可能导致未初始化的变量
- 合理使用默认导出:每个模块最多一个默认导出
- 保持一致的命名约定:文件名与默认导出内容一致
// 模块设计示例
// api.js - 负责API调用
export async function fetchUsers() { /* ... */ }
export async function fetchPosts() { /* ... */ }
// formatters.js - 负责数据格式化
export function formatDate(date) { /* ... */ }
export function formatCurrency(amount) { /* ... */ }
// index.js - 聚合模块,简化导入
export { fetchUsers, fetchPosts } from './api.js';
export { formatDate, formatCurrency } from './formatters.js';
export { default as utils } from './utils.js';
4、Proxy与Reflect
Proxy和Reflect是ES6引入的强大特性,允许拦截和自定义对象的基本操作。
Proxy基础
Proxy允许创建一个对象的代理,拦截并重新定义该对象的基本操作:
const target = {
name: '张三',
age: 30
};
const handler = {
// 拦截属性读取
get(target, prop, receiver) {
console.log(`正在获取${prop}属性`);
return target[prop];
},
// 拦截属性设置
set(target, prop, value, receiver) {
console.log(`正在设置${prop}属性为${value}`);
// 可以添加验证逻辑
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age必须是一个数字');
}
target[prop] = value;
return true; // 表示设置成功
}
};
// 创建代理
const proxy = new Proxy(target, handler);
// 使用代理
console.log(proxy.name); // 输出: 正在获取name属性 张三
proxy.age = 31; // 输出: 正在设置age属性为31
try {
proxy.age = '很老'; // 抛出TypeError
} catch (e) {
console.error(e.message);
}
Proxy提供13种可拦截的基本操作:
const handler = {
// 属性操作
get(target, prop, receiver) {},
set(target, prop, value, receiver) {},
has(target, prop) {}, // 拦截in操作符
deleteProperty(target, prop) {}, // 拦截delete操作
// 函数操作
apply(target, thisArg, argumentsList) {}, // 拦截函数调用
construct(target, args, newTarget) {}, // 拦截new操作
// 原型操作
getPrototypeOf(target) {},
setPrototypeOf(target, prototype) {},
// 扩展/配置
isExtensible(target) {},
preventExtensions(target) {},
// 属性描述符
getOwnPropertyDescriptor(target, prop) {},
defineProperty(target, prop, descriptor) {},
// 获取所有键
ownKeys(target) {} // 拦截Object.keys等
};
Reflect API
Reflect是一个内置对象,提供与Proxy处理程序方法相同的静态方法:
// 使用Reflect的Proxy
const handler = {
get(target, prop, receiver) {
console.log(`获取${prop}属性`);
// 使用Reflect.get代替target[prop]
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`设置${prop}属性`);
// 使用Reflect.set代替target[prop] = value
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy({x: 1, y: 2}, handler);
console.log(proxy.x); // 获取x属性 1
proxy.z = 3; // 设置z属性
使用Reflect的好处:
- 操作返回状态(成功/失败)而非抛出错误
- 接收相同的参数,方便与Proxy配合
- 提供函数式的对象操作API
Proxy实际应用
1. 数据验证
function createValidator(target, validations) {
return new Proxy(target, {
set(target, prop, value) {
if (validations[prop]) {
const validateFn = validations[prop];
if (!validateFn(value)) {
throw new Error(`Invalid value for ${prop}`);
}
}
target[prop] = value;
return true;
}
});
}
const user = createValidator(
{ name: '张三', age: 30 },
{
name: value => typeof value === 'string' && value.length > 0,
age: value => typeof value === 'number' && value >= 0 && value < 150
}
);
user.name = '李四'; // 有效
// user.age = -5; // 抛出错误
2. 响应式数据系统
// 简化版Vue 3响应式系统
function reactive(obj) {
return new Proxy(obj, {
get(target, prop, receiver) {
const result = Reflect.get(target, prop, receiver);
track(target, prop); // 跟踪依赖
// 深层响应式
return typeof result === 'object' && result !== null
? reactive(result)
: result;
},
set(target, prop, value, receiver) {
const oldValue = target[prop];
const result = Reflect.set(target, prop, value, receiver);
if (oldValue !== value) {
trigger(target, prop); // 触发更新
}
return result;
}
});
}
// 模拟实现
function track(target, prop) {
console.log(`追踪 ${prop} 变化`);
}
function trigger(target, prop) {
console.log(`属性 ${prop} 已变化,触发更新`);
}
const state = reactive({ count: 0, user: { name: '张三' } });
state.count++; // 触发更新
state.user.name = '李四'; // 深层响应式,触发更新
5、新的API与语法糖
除了主要特性外,现代JavaScript还引入了大量实用的API和语法糖。
数组新方法
// ES6+引入的数组方法
const numbers = [1, 2, 3, 4, 5];
// 查找元素
const found = numbers.find(n => n > 3); // 4
const foundIndex = numbers.findIndex(n => n > 3); // 3
// 检查包含
const includes = numbers.includes(3); // true
// 数组填充
const filled = new Array(5).fill(0); // [0, 0, 0, 0, 0]
const modified = [1, 2, 3, 4, 5].fill(0, 2, 4); // [1, 2, 0, 0, 5]
// 平铺数组
const nested = [1, [2, [3, 4]]];
const flat1 = nested.flat(); // [1, 2, [3, 4]]
const flatAll = nested.flat(Infinity); // [1, 2, 3, 4]
// 映射后平铺
const mappedAndFlat = [1, 2, 3].flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
// ES2022: 按索引位置替换元素,并返回替换的元素
const arr = ['a', 'b', 'c', 'd', 'e'];
console.log(arr.at(1)); // 'b'
console.log(arr.at(-1)); // 'e' (从末尾数)
对象新方法
// 对象新方法
const person = { name: '张三', age: 30, job: '工程师' };
const skills = { programming: true, design: false };
// Object.assign: 合并对象
const merged = Object.assign({}, person, skills);
// 对象属性描述
console.log(Object.getOwnPropertyDescriptors(person));
// 对象条目、值、键
console.log(Object.entries(person)); // [['name','张三'], ['age',30], ['job','工程师']]
console.log(Object.values(person)); // ['张三', 30, '工程师']
console.log(Object.keys(person)); // ['name', 'age', 'job']
// 从条目创建对象
const entries = [['name', '李四'], ['age', 25]];
const obj = Object.fromEntries(entries); // { name: '李四', age: 25 }
// 对象扩展运算符(ES2018)
const personWithSkills = { ...person, ...skills };
const { name, ...rest } = person; // 解构与剩余属性
字符串与正则新特性
// 字符串模板字面量
const name = '张三';
const greeting = `你好,${name}!`;
// 标签模板字面量
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<em>${values[i]}</em>` : '');
}, '');
}
const highlighted = highlight`我叫${name},我今年${30}岁。`;
// "我叫<em>张三</em>,我今年<em>30</em>岁。"
// 字符串新方法
console.log('Hello'.padStart(10, '*')); // '*****Hello'
console.log('Hello'.padEnd(10, '*')); // 'Hello*****'
console.log('Hello'.startsWith('He')); // true
console.log('Hello'.endsWith('lo')); // true
console.log('Hello'.includes('ll')); // true
console.log(' trim '.trim()); // 'trim'
console.log(' trim '.trimStart()); // 'trim '
console.log(' trim '.trimEnd()); // ' trim'
// 正则表达式命名捕获组(ES2018)
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = pattern.exec('2023-05-15');
console.log(match.groups.year); // '2023'
console.log(match.groups.month); // '05'
数值与数学增强
// 数值分隔符(ES2021)
const billion = 1_000_000_000; // 更易读
const bytes = 0xFF_FF_FF_FF; // 字节分组
// BigInt(ES2020)
const bigNumber = 9007199254740991n; // BigInt字面量
const anotherBig = BigInt('9007199254740991'); // 构造函数
console.log(bigNumber + 1n); // 9007199254740992n
// console.log(bigNumber + 1); // 错误:不能混合BigInt和Number
// 指数运算符
console.log(2 ** 10); // 1024
let num = 2;
num **= 3; // 8
// 数学方法
console.log(Math.trunc(3.9)); // 3 (去除小数部分)
console.log(Math.sign(-5)); // -1 (符号函数)
console.log(Math.log10(1000)); // 3 (底数为10的对数)
console.log(Math.log2(8)); // 3 (底数为2的对数)
其他语法特性
// 逻辑赋值运算符(ES2021)
let a = null;
a ||= 10; // 等同于 a = a || 10
console.log(a); // 10
let b = 5;
b &&= 20; // 等同于 b = b && 20
console.log(b); // 20
let c;
c ??= 30; // 等同于 c = c ?? 30
console.log(c); // 30
// 可选catch绑定(ES2019)
try {
// 可能抛出错误的代码
} catch { // 不需要错误变量时可以省略
console.log('发生错误');
}
// 顶层await(ES2022)
// 可以在模块顶层使用await,无需async函数包装
// module.js
const response = await fetch('https://api.example.com/data');
export const data = await response.json();
// 私有类字段和方法(ES2022)
class Counter {
#count = 0; // 私有字段
get value() {
return this.#count;
}
#increment() { // 私有方法
this.#count++;
}
increment() {
this.#increment();
}
}
ES2023 新特性
数组新增反向查找方法
- findLast() 和 findLastIndex():从数组末尾开始查找元素
const array = [1, 2, 3, 4, 5];
console.log(array.findLast(x => x % 2 === 1)); // 5
console.log(array.findLastIndex(x => x % 2 === 1)); // 4
数组的非修改性方法
以下方法返回新数组而不修改原数组:
- toReversed(): 返回反转后的新数组
- toSorted(): 返回排序后的新数组
- toSpliced(): 返回删除/替换元素后的新数组
- with(): 返回替换指定索引元素后的新数组
const arr = [1, 2, 3, 4, 5];
console.log(arr.toReversed()); // [5, 4, 3, 2, 1]
console.log(arr); // [1, 2, 3, 4, 5] - 原数组不变
console.log(arr.with(2, 10)); // [1, 2, 10, 4, 5]
Hashbang 语法
允许在JavaScript文件开头使用#!/usr/bin/env node
这样的语法,使JS文件可作为可执行脚本。
Symbol 作为 WeakMap 键
允许使用Symbol作为WeakMap的键,前提是该Symbol通过Symbol.for()创建。
ES2024 新特性
Promise.withResolvers()
提供了更简洁的Promise创建方式,返回promise及其控制函数:
const { promise, resolve, reject } = Promise.withResolvers();
// 等同于:
// let resolve, reject;
// const promise = new Promise((res, rej) => {
// resolve = res;
// reject = rej;
// });
数组分组方法
- Object.groupBy(): 按条件将数组元素分组为对象
- Map.groupBy(): 按条件将数组元素分组为Map
const people = [
{ name: "张三", age: 25 },
{ name: "李四", age: 30 },
{ name: "王五", age: 25 }
];
const groupedByAge = Object.groupBy(people, person => person.age);
// { "25": [{name:"张三",age:25}, {name:"王五",age:25}], "30": [{name:"李四",age:30}] }
Import 属性
允许在import语句中添加元数据:
import data from "./data.json" with { type: "json" };
装饰器(Decorators)
类装饰器正式标准化,可以修改或扩展类和类成员的行为:
function logged(value, { kind, name }) {
if (kind === "method") {
return function(...args) {
console.log(`正在调用 ${name} 方法`);
return value.call(this, ...args);
};
}
}
class Person {
@logged
greet() {
return "你好!";
}
}
String Unicode 处理
- isWellFormed(): 检查字符串是否包含有效的UTF-16代码点
- toWellFormed(): 替换无效代码点,返回格式正确的字符串
ArrayBuffer 传输
- transfer() 和 transferToFixedLength(): 转移ArrayBuffer所有权,避免内存复制
Temporal API
新的日期时间API,解决了现有Date对象的问题,提供更丰富的时间和日期操作功能。
RegExp Unicode 集合支持
新增v标志,支持Unicode属性转义和集合操作。
// 匹配所有希腊字母但不包括元音
const regex = /\p{Script=Greek}--[αεηιοωυ]/v;
6、兼容性与Polyfill
现代JavaScript特性在不同环境中的支持情况各不相同,需要通过工具确保代码的兼容性。
浏览器兼容性现状
主流浏览器已经支持大部分ES6+特性,但旧版浏览器不支持。Node.js各版本支持的特性也不同。
什么是Polyfill与转译
为了在不支持新特性的环境中使用现代JavaScript,我们需要两种解决方案:
- 转译(Transpilation): 将新语法转换为等效的旧语法
- Polyfill: 为缺失的API提供兼容实现
// 转译示例:箭头函数
// 现代代码
const add = (a, b) => a + b;
// 转译后
var add = function(a, b) {
return a + b;
};
// Polyfill示例:Array.prototype.includes
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
// 实现逻辑...
if (this === null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
var len = o.length >>> 0;
// 更多实现代码...
return false;
};
}
Babel: JavaScript转译器
Babel是最流行的JavaScript转译工具,能将现代JavaScript转为兼容性更好的代码:
// babel.config.js
module.exports = {
presets: [
["@babel/preset-env", {
targets: {
browsers: ["> 1%", "last 2 versions", "not dead"],
node: "10"
},
useBuiltIns: "usage",
corejs: 3
}]
],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
};
Babel工作流程:
常用Polyfill库
- core-js: 最全面的JavaScript标准库polyfill
- regenerator-runtime: 为生成器和async/await提供运行时支持
- whatwg-fetch: Fetch API的polyfill
- promise-polyfill: Promise的polyfill实现
// 手动引入polyfill
import "core-js/stable";
import "regenerator-runtime/runtime";
import "whatwg-fetch";
// 或者在webpack配置中
entry: ["core-js/stable", "regenerator-runtime/runtime", "./src/index.js"]
构建工具与兼容性
现代前端工具链通常集成了转译和polyfill功能:
// webpack.config.js
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: '> 0.25%, not dead',
useBuiltIns: 'usage',
corejs: 3
}]
]
}
}
}
]
}
};
差异化加载策略
为现代浏览器提供现代代码,为旧浏览器提供兼容代码,可以优化性能:
<!-- 现代浏览器使用模块版本 -->
<script type="module" src="app.modern.js"></script>
<!-- 旧浏览器使用nomodule后备 -->
<script nomodule src="app.legacy.js"></script>
// 在Webpack或Rollup中配置差异化构建
// webpack.config.js
module.exports = [
{
// 现代浏览器构建
output: {
filename: '[name].modern.js'
},
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
targets: { esmodules: true }
}
}
}]
}
},
{
// 旧浏览器构建
output: {
filename: '[name].legacy.js'
},
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
targets: '> 0.25%, not dead'
}
}
}]
}
}
];
特性检测
除了构建时处理兼容性,还可以在运行时检测特性并提供替代方案:
// 特性检测示例
if ('IntersectionObserver' in window) {
// 使用IntersectionObserver
const observer = new IntersectionObserver(entries => {
// ...
});
} else {
// 降级处理,如监听scroll事件
window.addEventListener('scroll', handleScroll);
}
// 动态加载polyfill
(async function() {
if (!window.fetch) {
await import('whatwg-fetch');
}
// 现在可以安全使用fetch
fetch('/api/data').then(/* ... */);
})();
总结:现代JavaScript特性的价值
现代JavaScript极大改善了开发体验,使代码更加简洁、可维护,同时提高了应用性能和可扩展性。主要优势包括:
开发效率提升
- 更简洁的语法:箭头函数、解构赋值、模板字符串等减少了样板代码
- 强大的异步处理:Promise、async/await让异步代码更易理解和维护
- 模块化系统:原生支持模块化开发,代码组织更合理
代码质量改善
- 更好的作用域控制:let/const减少作用域问题
- 不可变编程支持:扩展运算符简化了不可变数据更新
- 可选链与空值合并:大幅减少防御性编程的样板代码
可维护性提高
- 类语法:更直观的面向对象编程模型
- 模块化:明确的依赖关系,降低耦合
- 标准化的API:统一的实现,减少框架依赖