ES6+ 新特性
问题
ES6 及之后版本有哪些重要的新特性?如何在实际项目中应用?
答案
ES6(ES2015)是 JavaScript 的重大更新,引入了众多新特性。之后每年都有新版本发布,持续完善语言功能。
ES6 核心特性
let 和 const
// let: 块级作用域,可重新赋值
let count = 0;
count = 1;
// const: 块级作用域,不可重新赋值
const PI = 3.14159;
// PI = 3; // ❌ TypeError
// const 对象属性可修改
const user = { name: 'Alice' };
user.name = 'Bob'; // ✅
箭头函数
// 简洁语法
const add = (a: number, b: number): number => a + b;
// 单参数可省略括号
const double = (x: number): number => x * 2;
// 多行需要花括号和 return
const calculate = (a: number, b: number): number => {
const result = a + b;
return result * 2;
};
// 返回对象需要括号
const createUser = (name: string) => ({ name, id: Date.now() });
// 箭头函数没有自己的 this
const obj = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(this.name); // 'Alice' - 继承外层 this
}, 100);
}
};
模板字符串
const name = 'Alice';
const age = 25;
// 字符串插值
const greeting = `Hello, ${name}! You are ${age} years old.`;
// 多行字符串
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// 表达式
const message = `${age >= 18 ? 'Adult' : 'Minor'}`;
// 标签模板
function highlight(strings: TemplateStringsArray, ...values: unknown[]): string {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const text = highlight`Hello, ${name}!`; // 'Hello, <mark>Alice</mark>!'
解构赋值
// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1, 2, [3, 4, 5]
// 默认值
const [a = 0, b = 0] = [1];
console.log(a, b); // 1, 0
// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];
// 对象解构
const user = { name: 'Alice', age: 25, city: 'Beijing' };
const { name, age } = user;
// 重命名
const { name: userName } = user;
console.log(userName); // 'Alice'
// 默认值
const { country = 'China' } = user;
console.log(country); // 'China'
// 嵌套解构
const data = { user: { profile: { avatar: 'url' } } };
const { user: { profile: { avatar } } } = data;
// 函数参数解构
function greet({ name, age = 0 }: { name: string; age?: number }): void {
console.log(`${name}, ${age}`);
}
展开运算符
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 数组复制(浅拷贝)
const copy = [...arr1];
// 合并数组
const merged = [...arr1, ...arr2];
// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 对象合并(后者覆盖前者)
const defaults = { theme: 'dark', lang: 'en' };
const settings = { ...defaults, lang: 'zh' }; // { theme: 'dark', lang: 'zh' }
// 函数参数展开
const numbers = [1, 2, 3, 4, 5];
Math.max(...numbers); // 5
剩余参数
// 收集剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// 与其他参数结合
function log(level: string, ...messages: unknown[]): void {
console.log(`[${level}]`, ...messages);
}
类
class Animal {
// 私有字段(ES2022)
#name: string;
// 静态属性
static kingdom = 'Animalia';
constructor(name: string) {
this.#name = name;
}
// getter/setter
get name(): string {
return this.#name;
}
set name(value: string) {
this.#name = value;
}
// 方法
speak(): void {
console.log(`${this.#name} makes a sound`);
}
// 静态方法
static create(name: string): Animal {
return new Animal(name);
}
}
// 继承
class Dog extends Animal {
constructor(name: string) {
super(name);
}
speak(): void {
console.log(`${this.name} barks`);
}
}
Promise
// 创建 Promise
const promise = new Promise<string>((resolve, reject) => {
setTimeout(() => resolve('success'), 100);
});
// 使用
promise
.then((value) => console.log(value))
.catch((error) => console.error(error))
.finally(() => console.log('done'));
// Promise 静态方法
Promise.all([p1, p2, p3]); // 全部成功
Promise.race([p1, p2, p3]); // 第一个完成
Promise.allSettled([p1, p2, p3]); // 全部完成
Promise.any([p1, p2, p3]); // 第一个成功
模块
// 导出
export const PI = 3.14159;
export function add(a: number, b: number): number {
return a + b;
}
export default class Calculator {}
// 导入
import Calculator, { PI, add } from './math';
import * as math from './math';
import { add as addNumbers } from './math';
ES7+ 新特性
ES2016 (ES7)
// Array.prototype.includes
[1, 2, 3].includes(2); // true
// 指数运算符
2 ** 10; // 1024
ES2017 (ES8)
// async/await
async function fetchData(): Promise<string> {
const response = await fetch('/api');
return response.text();
}
// Object.values/entries
const obj = { a: 1, b: 2 };
Object.values(obj); // [1, 2]
Object.entries(obj); // [['a', 1], ['b', 2]]
// 字符串填充
'5'.padStart(3, '0'); // '005'
'5'.padEnd(3, '0'); // '500'
ES2018 (ES9)
// 异步迭代
async function* asyncGenerator(): AsyncGenerator<number> {
yield 1;
yield 2;
}
for await (const value of asyncGenerator()) {
console.log(value);
}
// 对象 rest/spread
const { a, ...rest } = { a: 1, b: 2, c: 3 };
console.log(rest); // { b: 2, c: 3 }
// Promise.finally
promise.finally(() => console.log('done'));
ES2019 (ES10)
// Array.flat/flatMap
[1, [2, [3]]].flat(2); // [1, 2, 3]
[1, 2].flatMap(x => [x, x * 2]); // [1, 2, 2, 4]
// Object.fromEntries
Object.fromEntries([['a', 1], ['b', 2]]); // { a: 1, b: 2 }
// 字符串 trimStart/trimEnd
' hello '.trimStart(); // 'hello '
' hello '.trimEnd(); // ' hello'
// 可选 catch 参数
try {
throw new Error();
} catch { // 不需要参数
console.log('error');
}
ES2020 (ES11)
// 可选链 ?.
const user = { profile: { avatar: 'url' } };
user?.profile?.avatar; // 'url'
user?.settings?.theme; // undefined
// 空值合并 ??
const value = null ?? 'default'; // 'default'
const zero = 0 ?? 'default'; // 0(只有 null/undefined 触发)
// BigInt
const big = 9007199254740993n;
// Promise.allSettled
Promise.allSettled([p1, p2, p3]);
// globalThis
globalThis.setTimeout; // 跨环境访问全局对象
// 动态 import
const module = await import('./module.js');
ES2021 (ES12)
// 逻辑赋值运算符
let a = null;
a ||= 'default'; // a = a || 'default'
a &&= 'value'; // a = a && 'value'
a ??= 'default'; // a = a ?? 'default'
// 数字分隔符
const billion = 1_000_000_000;
// String.replaceAll
'aaa'.replaceAll('a', 'b'); // 'bbb'
// Promise.any
Promise.any([p1, p2, p3]); // 第一个成功的
ES2022 (ES13)
// 类私有字段
class MyClass {
#privateField = 42;
#privateMethod(): void {}
}
// 顶层 await
const data = await fetch('/api').then(r => r.json());
// at() 方法
[1, 2, 3].at(-1); // 3
'hello'.at(-1); // 'o'
// Object.hasOwn
Object.hasOwn({ a: 1 }, 'a'); // true(替代 hasOwnProperty)
// Error cause
throw new Error('Failed', { cause: originalError });
ES2023 (ES14)
// 数组方法(不改变原数组)
const arr = [3, 1, 2];
arr.toSorted(); // [1, 2, 3]
arr.toReversed(); // [2, 1, 3]
arr.toSpliced(1, 1); // [3, 2]
arr.with(1, 10); // [3, 10, 2]
// findLast / findLastIndex
[1, 2, 3, 2].findLast(x => x === 2); // 2
[1, 2, 3, 2].findLastIndex(x => x === 2); // 3
特性对比表
| 版本 | 年份 | 主要特性 |
|---|---|---|
| ES6 | 2015 | let/const、箭头函数、类、Promise、模块 |
| ES7 | 2016 | includes、指数运算符 |
| ES8 | 2017 | async/await、Object.values/entries |
| ES9 | 2018 | 异步迭代、对象 rest/spread |
| ES10 | 2019 | flat/flatMap、fromEntries |
| ES11 | 2020 | 可选链、空值合并、BigInt |
| ES12 | 2021 | 逻辑赋值、replaceAll |
| ES13 | 2022 | 私有字段、顶层 await、at() |
| ES14 | 2023 | toSorted、toReversed、findLast |
常见面试问题
Q1: 箭头函数和普通函数的区别?
答案:
| 特性 | 箭头函数 | 普通函数 |
|---|---|---|
| this | 继承外层 | 动态绑定 |
| arguments | 无 | 有 |
| new 调用 | 不能 | 可以 |
| prototype | 无 | 有 |
| yield | 不能 | 可以(Generator) |
Q2: ?? 和 || 的区别?
答案:
// || 对所有假值生效
0 || 'default'; // 'default'
'' || 'default'; // 'default'
false || 'default'; // 'default'
null || 'default'; // 'default'
// ?? 只对 null/undefined 生效
0 ?? 'default'; // 0
'' ?? 'default'; // ''
false ?? 'default'; // false
null ?? 'default'; // 'default'
Q3: 可选链有哪些用法?
答案:
// 1. 访问属性
obj?.property;
// 2. 访问动态属性
obj?.[expression];
// 3. 调用方法
obj?.method?.();
// 4. 访问数组元素
arr?.[index];
// 组合使用
user?.profile?.settings?.theme ?? 'dark';
Q4: 解构赋值有哪些实用技巧?
答案:
// 1. 交换变量
[a, b] = [b, a];
// 2. 函数多返回值
function getCoords(): [number, number] {
return [10, 20];
}
const [x, y] = getCoords();
// 3. 函数参数默认值
function fetch({ url, method = 'GET' }: { url: string; method?: string }): void {}
// 4. 提取需要的属性
const { name, email } = user;
// 5. 重命名
const { name: userName, age: userAge } = user;
Q5: 如何用展开运算符实现浅拷贝?
答案:
// 数组浅拷贝
const arrCopy = [...originalArr];
// 对象浅拷贝
const objCopy = { ...originalObj };
// 合并对象
const merged = { ...obj1, ...obj2 };
// ⚠️ 注意:只是浅拷贝
const original = { user: { name: 'Alice' } };
const copy = { ...original };
copy.user.name = 'Bob';
console.log(original.user.name); // 'Bob' - 被修改了