Appearance
ES6+ 新特性
ES6(ECMAScript 2015)及后续版本引入了许多重要特性,极大地改善了JavaScript的开发体验。这些特性包括语法糖、新的数据结构、模块系统等。
ES6 (2015) 核心特性
箭头函数 (Arrow Functions)
javascript
// 传统函数表达式
const add = function(a, b) {
return a + b;
};
// 箭头函数
const add = (a, b) => a + b;
// 单个参数可省略括号
const square = x => x * x;
// 无参数需空括号
const getRandom = () => Math.random();
// 多行函数体需大括号和显式return
const complexFunction = (a, b) => {
const result = a * b;
return result > 10 ? result : 0;
};
// 箭头函数的this绑定
const obj = {
name: 'Alice',
regularFunction: function() {
console.log(this.name); // 'Alice'
const arrowFunction = () => {
console.log(this.name); // 'Alice',继承外层this
};
arrowFunction();
}
};
模板字符串 (Template Literals)
javascript
const name = 'John';
const age = 30;
// 多行字符串
const multiline = `
这是
多行
字符串
`;
// 字符串插值
const greeting = `Hello, ${name}! You are ${age} years old.`;
// 表达式插值
const result = `2 + 2 = ${2 + 2}`;
const upperName = `Name: ${name.toUpperCase()}`;
// 标签模板
function highlight(strings, ...values) {
return strings.reduce((result, string, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : '';
return result + string + value;
}, '');
}
const name = 'John';
const age = 30;
const html = highlight`<p>Hello, ${name}. You are ${age} years old.</p>`;
// 结果: '<p>Hello, <mark>John</mark>. You are <mark>30</mark> years old.</p>'
解构赋值 (Destructuring)
javascript
// 数组解构
const numbers = [1, 2, 3, 4, 5];
const [first, second, third] = numbers;
console.log(first, second, third); // 1, 2, 3
// 跳过元素
const [a, , c] = [1, 2, 3];
console.log(a, c); // 1, 3
// 剩余元素
const [x, y, ...rest] = [1, 2, 3, 4, 5];
console.log(x, y, rest); // 1, 2, [3, 4, 5]
// 默认值
const [p, q, r = 10] = [1, 2];
console.log(r); // 10
// 交换变量
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1
// 对象解构
const person = { name: 'John', age: 30, city: 'New York' };
const { name, age } = person;
console.log(name, age); // 'John', 30
// 重命名
const { name: personName, age: personAge } = person;
console.log(personName, personAge); // 'John', 30
// 默认值
const { name, country = 'USA' } = person;
console.log(country); // 'USA'
// 嵌套解构
const user = {
name: 'John',
address: {
city: 'New York',
zip: '10001'
}
};
const { address: { city, zip } } = user;
console.log(city, zip); // 'New York', '10001'
// 函数参数解构
function displayUser({ name, age, city = 'Unknown' }) {
console.log(`Name: ${name}, Age: ${age}, City: ${city}`);
}
displayUser({ name: 'John', age: 30 }); // 'Name: John, Age: 30, City: Unknown'
let 和 const (块级作用域)
javascript
// var - 函数作用域
function varExample() {
if (true) {
var x = 1;
}
console.log(x); // 1 - var有函数作用域
}
// let - 块级作用域
function letExample() {
if (true) {
let y = 1;
}
// console.log(y); // ReferenceError: y is not defined
}
// const - 块级作用域,不可重新赋值
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable
// const对象和数组
const obj = { name: 'John' };
obj.name = 'Jane'; // 有效,修改对象属性
obj.age = 30; // 有效,添加属性
// obj = {}; // 错误!不能重新赋值整个对象
// 暂时性死区
// console.log(temp); // ReferenceError: Cannot access 'temp' before initialization
let temp = 'value';
// 循环中的闭包问题解决
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 0, 1, 2(使用let)
}, 100);
}
默认参数 (Default Parameters)
javascript
function greet(name = 'World', punctuation = '!') {
return `Hello, ${name}${punctuation}`;
}
console.log(greet()); // 'Hello, World!'
console.log(greet('John')); // 'Hello, John!'
console.log(greet('John', '?')); // 'Hello, John?'
// 默认参数可以是表达式
function createPoint(x = 0, y = x) {
return { x, y };
}
// 默认参数可以引用之前的参数
function multiply(a, b = a, c = a * b) {
return [a, b, c];
}
console.log(multiply(2)); // [2, 2, 4]
扩展运算符 (Spread Operator)
javascript
// 数组扩展
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 复制数组
const original = [1, 2, 3];
const copy = [...original];
// 合并数组
const even = [2, 4, 6];
const odd = [1, 3, 5];
const all = [...odd, ...even]; // [1, 3, 5, 2, 4, 6]
// 函数参数(剩余参数)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 对象扩展
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
// 对象复制
const originalObj = { name: 'John', age: 30 };
const copiedObj = { ...originalObj };
// 在函数调用中使用扩展运算符
const numbers = [1, 2, 3, 4, 5];
Math.max(...numbers); // 等价于 Math.max(1, 2, 3, 4, 5)
类 (Classes)
javascript
// ES6类语法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
introduce() {
return `Hi, I'm ${this.name} and I'm ${this.age} years old.`;
}
static species() {
return 'Homo sapiens';
}
get birthYear() {
return new Date().getFullYear() - this.age;
}
set age(value) {
if (value < 0) {
throw new Error('Age cannot be negative');
}
this._age = value;
}
get age() {
return this._age;
}
}
const john = new Person('John', 30);
console.log(john.greet()); // 'Hello, I'm John'
console.log(Person.species()); // 'Homo sapiens'
// 类继承
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 调用父类构造函数
this.grade = grade;
}
study() {
return `${this.name} is studying in grade ${this.grade}`;
}
introduce() {
return `${super.introduce()} I'm a student in grade ${this.grade}.`;
}
}
const alice = new Student('Alice', 20, 12);
console.log(alice.introduce()); // 'Hi, I'm Alice and I'm 20 years old. I'm a student in grade 12.'
模块 (Modules)
javascript
// 导出 (export)
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export default class Calculator {
multiply(a, b) {
return a * b;
}
}
// 命名导出
export { PI, add };
// 导入 (import)
// main.js
import Calculator, { PI, add } from './math.js';
import { add as addNumbers } from './math.js'; // 重命名
import * as math from './math.js'; // 整体导入
import './math.js'; // 只执行模块,不导入任何内容
// 动态导入 (ES2020)
async function loadModule() {
const { add } = await import('./math.js');
return add(2, 3);
}
Promise
javascript
// Promise基础
function asyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('操作成功');
} else {
reject(new Error('操作失败'));
}
}, 1000);
});
}
asyncOperation()
.then(result => console.log(result))
.catch(error => console.error(error.message));
// Promise链
Promise.resolve(1)
.then(value => value * 2)
.then(value => value + 3)
.then(value => console.log(value)); // 5
// 并行执行Promise
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(([users, posts, comments]) => {
// 所有请求都完成
});
ES2016 (ES7) 特性
指数运算符
javascript
// 传统的幂运算
Math.pow(2, 3); // 8
// ES2016指数运算符
2 ** 3; // 8
let base = 2;
base **= 3; // 等价于 base = base ** 3
Array.prototype.includes
javascript
const arr = [1, 2, 3, 4, 5];
// 传统方法
arr.indexOf(3) !== -1; // true
// ES2016方法
arr.includes(3); // true
arr.includes(6); // false
// 检测NaN(indexOf无法检测NaN)
const hasNaN = [1, 2, NaN].includes(NaN); // true
ES2017 (ES8) 特性
async/await
javascript
// 传统Promise写法
function fetchUserData() {
return fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(response => response.json())
.then(posts => ({ user, posts }));
}
// async/await写法
async function fetchUserDataAsync() {
try {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error('获取用户数据失败:', error);
}
}
Object.values() 和 Object.entries()
javascript
const obj = { a: 1, b: 2, c: 3 };
// Object.values()
const values = Object.values(obj); // [1, 2, 3]
// Object.entries()
const entries = Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]
// 遍历对象
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`);
}
Object.getOwnPropertyDescriptors()
javascript
const obj = {
get name() { return 'John'; },
set name(value) { this._name = value; }
};
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors.name); // 包含get、set、enumerable、configurable等属性描述符
String padding
javascript
// String.padStart()
'5'.padStart(3, '0'); // '005'
'12'.padStart(3, '0'); // '012'
// String.padEnd()
'5'.padEnd(3, '0'); // '500'
'abc'.padEnd(6, '.'); // 'abc...'
// 实际应用:格式化时间
function formatTime(hours, minutes, seconds) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
ES2018 (ES9) 特性
异步迭代器
javascript
// 异步可迭代对象
class AsyncQueue {
constructor() {
this.items = [];
this.waiting = [];
}
push(item) {
if (this.waiting.length > 0) {
const resolver = this.waiting.shift();
resolver({ done: false, value: item });
} else {
this.items.push(item);
}
}
async *[Symbol.asyncIterator]() {
while (true) {
if (this.items.length > 0) {
yield this.items.shift();
} else {
await new Promise(resolve => {
this.waiting.push(resolve);
});
}
}
}
}
// 使用异步迭代
async function processQueue() {
const queue = new AsyncQueue();
setTimeout(() => queue.push('item1'), 1000);
setTimeout(() => queue.push('item2'), 2000);
for await (const item of queue) {
console.log('Processing:', item);
}
}
Promise.prototype.finally()
javascript
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
// 无论成功还是失败都会执行
console.log('请求完成');
});
Rest/Spread Properties
javascript
// 对象解构中的Rest属性
const { name, age, ...otherProps } = { name: 'John', age: 30, city: 'NYC', country: 'USA' };
console.log(name, age); // 'John', 30
console.log(otherProps); // { city: 'NYC', country: 'USA' }
// 对象扩展
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const obj3 = { ...obj1, ...obj2, e: 5 }; // { a: 1, b: 2, c: 3, d: 4, e: 5 }
ES2019 (ES10) 特性
Array.flat() 和 Array.flatMap()
javascript
// Array.flat() - 数组扁平化
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat(); // [1, 2, 3, 4, [5, 6]]
nested.flat(2); // [1, 2, 3, 4, 5, 6]
nested.flat(Infinity); // [1, 2, 3, 4, 5, 6] - 完全扁平化
// Array.flatMap() - map + flat
const arr = [1, 2, 3];
const doubledAndFlat = arr.flatMap(n => [n, n * 2]);
// 等价于 arr.map(n => [n, n * 2]).flat()
// 结果: [1, 2, 2, 4, 3, 6]
Object.fromEntries()
javascript
// 将键值对数组转换为对象
const entries = [['name', 'John'], ['age', 30], ['city', 'NYC']];
const obj = Object.fromEntries(entries);
// { name: 'John', age: 30, city: 'NYC' }
// 实际应用:过滤对象属性
const originalObj = { a: 1, b: 2, c: 3, d: 4 };
const filteredObj = Object.fromEntries(
Object.entries(originalObj).filter(([key, value]) => value % 2 === 0)
);
// { b: 2, d: 4 }
String.trimStart() 和 String.trimEnd()
javascript
const str = ' hello world ';
str.trimStart(); // 'hello world '
str.trimEnd(); // ' hello world'
str.trimLeft(); // 'hello world ' (别名)
str.trimRight(); // ' hello world' (别名)
ES2020 (ES11) 特性
可选链操作符 (Optional Chaining)
javascript
const user = {
name: 'John',
address: {
street: '123 Main St',
city: 'New York'
}
};
// 传统方式
const city = user && user.address && user.address.city;
// 可选链操作符
const city = user?.address?.city; // 'New York'
const undefinedValue = user?.profile?.email; // undefined(不会报错)
// 方法调用
user?.getName?.(); // 如果getName存在则调用
user?.address?.toString?.(); // 安全调用方法
空值合并操作符 (Nullish Coalescing)
javascript
// 传统逻辑或操作符
const value1 = null || 'default'; // 'default'
const value2 = 0 || 'default'; // 'default'(0是falsy值)
// 空值合并操作符 - 只有null和undefined才使用默认值
const value1 = null ?? 'default'; // 'default'
const value2 = 0 ?? 'default'; // 0(0不是null或undefined)
// 实际应用
function createUser(options) {
return {
name: options.name ?? 'Anonymous',
age: options.age ?? 0,
active: options.active ?? true
};
}
BigInt
javascript
// 创建BigInt
const bigNumber1 = 123456789012345678901234567890n;
const bigNumber2 = BigInt('123456789012345678901234567890');
const bigNumber3 = BigInt(1234567890);
// BigInt运算
const result = bigNumber1 + bigNumber2;
const product = bigNumber1 * bigNumber2;
// 注意:BigInt不能与Number混合运算
// 1n + 2; // TypeError
1n + BigInt(2); // 3n
globalThis
javascript
// 在不同环境中获取全局对象
// 在浏览器中:globalThis === window
// 在Node.js中:globalThis === global
// 在Web Workers中:globalThis === self
function getGlobal() {
if (typeof globalThis !== 'undefined') return globalThis;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof self !== 'undefined') return self;
throw new Error('无法获取全局对象');
}
// 现在可以简单地使用
const global = globalThis;
Dynamic Import
javascript
// 动态导入模块
async function loadModule() {
const { add } = await import('./math.js');
return add(2, 3);
}
// 条件导入
async function loadFeature() {
if (window.location.search.includes('advanced=true')) {
const { advancedFeature } = await import('./advanced.js');
return advancedFeature();
} else {
const { basicFeature } = await import('./basic.js');
return basicFeature();
}
}
// 按需导入
document.getElementById('loadButton').addEventListener('click', async () => {
const { renderChart } = await import('./chart.js');
renderChart();
});
ES2021 (ES12) 特性
逻辑赋值操作符
javascript
// 逻辑与赋值
a &&= b; // 等价于 if(a) { a = b; }
// 逻辑或赋值
a ||= b; // 等价于 if(!a) { a = b; }
// 空值合并赋值
a ??= b; // 等价于 if(a === null || a === undefined) { a = b; }
// 实际应用
let user = {};
user.name ||= 'Anonymous'; // 如果name不存在或falsy,则设置为'Anonymous'
user.settings ??= {}; // 如果settings为null或undefined,则设置为空对象
数字分隔符
javascript
// 提高大数字的可读性
const billion = 1_000_000_000; // 10亿
const creditCard = 1234_5678_9012_3456; // 信用卡号
const binary = 0b1010_0001_1000_0101; // 二进制
const hex = 0xA0_B0_C0; // 十六进制
const decimal = 1_000_000.567_890; // 小数
// 注意:分隔符不能在数字开头、结尾或连续使用
// _1000; // 语法错误
// 1000_; // 语法错误
// 1_000__000; // 语法错误
ES2022 (ES13) 特性
Object.hasOwn()
javascript
const obj = { name: 'John' };
// 传统方式(不安全)
Object.prototype.hasOwnProperty.call(obj, 'name'); // true
// ES2022方式(更安全)
Object.hasOwn(obj, 'name'); // true
// 当对象重写了hasOwnProperty方法时
const dangerousObj = {
hasOwnProperty: function() {
return false;
},
name: 'John'
};
Object.prototype.hasOwnProperty.call(dangerousObj, 'name'); // true
Object.hasOwn(dangerousObj, 'name'); // true
Error Cause
javascript
// 在错误中包含原因
async function processFile() {
try {
const data = await readFile('config.json');
return JSON.parse(data);
} catch (error) {
// 创建新的错误,包含原始错误作为原因
throw new Error('配置文件处理失败', { cause: error });
}
}
// 捕获并访问错误原因
try {
await processFile();
} catch (error) {
console.error('主要错误:', error.message);
console.error('根本原因:', error.cause?.message);
}
Top-level await
javascript
// 在模块顶层使用await(无需包装在async函数中)
const response = await fetch('/api/config');
const config = await response.json();
// 条件加载
const module = await (Math.random() > 0.5
? import('./feature-a.js')
: import('./feature-b.js'));
// 实际应用:模块初始化
const dbConnection = await createDatabaseConnection();
const cache = await initializeCache();
ES2023 (ES14) 特性
Array.findLast() 和 Array.findLastIndex()
javascript
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
// 查找最后一个偶数
const lastEven = numbers.findLast(n => n % 2 === 0); // 4
// 查找最后一个偶数的索引
const lastEvenIndex = numbers.findLastIndex(n => n % 2 === 0); // 5
// 传统方式需要反转数组
const lastEvenTraditional = [...numbers].reverse().find(n => n % 2 === 0); // 4
// 但这样会改变原数组的顺序,findLast更高效
对象原型的 hasOwnPropert 方法
(已在ES2022中介绍 Object.hasOwn)
使用这些特性的最佳实践
1. 渐进式采用
javascript
// 检查浏览器支持
if (typeof Array.prototype.flat !== 'undefined') {
// 使用flat方法
const flattened = nestedArray.flat();
} else {
// 使用polyfill或替代方案
const flattened = flatten(nestedArray);
}
2. 工具链配置
javascript
// Babel配置示例
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions"]
},
"useBuiltIns": "usage",
"corejs": 3
}]
]
}
ES6+的新特性大大提升了JavaScript的开发效率和代码可读性。在实际项目中,应根据目标环境和团队技术栈选择合适的特性使用。