JavaScript ES6新特性一览:现代前端开发的基石
0x00 引言:JavaScript的演进
2015年6月,ECMAScript 6(ES6,正式名称ES2015)发布,这是JavaScript历史上最重要的一次更新。它引入了类、模块、箭头函数、Promise等现代编程语言的特性,彻底改变了前端开发的范式。
如今,ES6+(包括ES2016-ES2023)已成为前端开发的标配。理解这些特性不仅能写出更优雅的代码,更能深入理解现代框架(React、Vue)的底层原理。
本文将系统梳理ES6的核心特性,从语法糖到底层机制,帮助你构建完整的知识体系。
0x01 let与const:块级作用域
1.1 var的问题
ES5的var存在三大问题:
问题1:变量提升(Hoisting)
console.log(x); // undefined(而非ReferenceError)
var x = 10;
// 实际执行顺序
var x;
console.log(x);
x = 10;问题2:无块级作用域
if (true) {
var x = 10;
}
console.log(x); // 10(泄漏到外部)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(闭包陷阱)问题3:重复声明
var x = 10;
var x = 20; // 不报错,直接覆盖1.2 let:块级作用域变量
// 块级作用域
if (true) {
let x = 10;
}
console.log(x); // ReferenceError
// 解决闭包问题
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2
// 暂时性死区(TDZ)
console.log(x); // ReferenceError(不是undefined)
let x = 10;1.3 const:常量声明
const PI = 3.14159;
PI = 3.14; // TypeError: Assignment to constant variable
// 注意:const只保证引用不变,对象内容可变
const obj = { x: 1 };
obj.x = 2; // 合法
obj = {}; // TypeError
// 冻结对象内容
const frozen = Object.freeze({ x: 1 });
frozen.x = 2; // 静默失败(严格模式报错)最佳实践:
- 优先使用
const(90%的场景) - 需要重新赋值时用
let - 完全避免
var
0x02 箭头函数:简洁的函数表达式
2.1 语法简化
// ES5
var add = function(a, b) {
return a + b;
};
// ES6:基本形式
const add = (a, b) => {
return a + b;
};
// 简写:单表达式自动返回
const add = (a, b) => a + b;
// 单参数可省略括号
const square = x => x * x;
// 无参数
const random = () => Math.random();
// 返回对象字面量需要括号
const makePoint = (x, y) => ({ x, y });2.2 this绑定的革命
ES5的this陷阱:
function Timer() {
this.seconds = 0;
setInterval(function() {
this.seconds++; // this指向window,而非Timer实例
}, 1000);
}
// 传统解决方案
function Timer() {
var self = this;
setInterval(function() {
self.seconds++;
}, 1000);
}ES6箭头函数:
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 词法this,继承外层作用域
}, 1000);
}React类组件的应用:
class Button extends React.Component {
// 错误:this丢失
handleClick() {
console.log(this); // undefined
}
// 方案1:构造函数绑定
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
// 方案2:箭头函数(推荐)
handleClick = () => {
console.log(this); // 正确指向组件实例
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}2.3 不适合箭头函数的场景
// 1. 对象方法
const obj = {
name: 'Alice',
greet: () => {
console.log(this.name); // undefined,this指向全局
}
};
// 应该用普通函数
const obj = {
name: 'Alice',
greet() {
console.log(this.name); // 'Alice'
}
};
// 2. 构造函数
const Person = (name) => {
this.name = name; // TypeError: 箭头函数没有constructor
};
// 3. 需要动态this的场景
button.addEventListener('click', () => {
console.log(this); // window,而非button元素
});0x03 模板字符串:强大的字符串处理
3.1 基本用法
// 多行字符串
const html = `
<div>
<h1>Title</h1>
<p>Content</p>
</div>
`;
// 插值表达式
const name = 'Alice';
const age = 30;
console.log(`${name} is ${age} years old`);
// 表达式计算
console.log(`1 + 1 = ${1 + 1}`);
// 嵌套模板
const nested = `Outer ${`Inner ${42}`}`;3.2 标签模板(Tagged Templates)
// 自定义模板处理器
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const name = 'Alice';
const age = 30;
const output = highlight`Name: ${name}, Age: ${age}`;
// "Name: <mark>Alice</mark>, Age: <mark>30</mark>"实战应用:SQL防注入
function sql(strings, ...values) {
return strings.reduce((query, str, i) => {
const value = values[i];
const escaped = typeof value === 'string'
? value.replace(/'/g, "''")
: value;
return query + str + (escaped !== undefined ? `'${escaped}'` : '');
}, '');
}
const username = "admin' OR '1'='1";
const query = sql`SELECT * FROM users WHERE name = ${username}`;
// "SELECT * FROM users WHERE name = 'admin'' OR ''1''=''1'"0x04 解构赋值:优雅的数据提取
4.1 数组解构
// 基本用法
const [a, b, c] = [1, 2, 3];
// 跳过元素
const [first, , third] = [1, 2, 3];
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4];
// head: 1, tail: [2, 3, 4]
// 默认值
const [x = 0, y = 0] = [10];
// x: 10, y: 0
// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];4.2 对象解构
// 基本用法
const { name, age } = { name: 'Alice', age: 30 };
// 重命名
const { name: userName, age: userAge } = user;
// 默认值
const { name = 'Anonymous', age = 0 } = {};
// 嵌套解构
const { address: { city, country } } = {
address: { city: 'Beijing', country: 'China' }
};
// 剩余属性
const { name, ...rest } = { name: 'Alice', age: 30, city: 'NYC' };
// rest: { age: 30, city: 'NYC' }4.3 函数参数解构
// 传统方式
function greet(user) {
console.log(`Hello, ${user.name}!`);
}
// 解构参数
function greet({ name, age }) {
console.log(`Hello, ${name} (${age} years old)!`);
}
// 默认值
function greet({ name = 'Guest', age = 0 } = {}) {
console.log(`Hello, ${name}!`);
}
// React组件中的应用
function UserCard({ name, avatar, bio = 'No bio available' }) {
return (
<div>
<img src={avatar} />
<h3>{name}</h3>
<p>{bio}</p>
</div>
);
}0x05 展开运算符:数据的拷贝与合并
5.1 数组展开
// 数组拷贝(浅拷贝)
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
// 数组合并
const combined = [...arr1, ...arr2, 4, 5];
// 函数参数展开
Math.max(...[1, 5, 3, 9, 2]); // 9
// 将类数组转为真数组
const nodeList = document.querySelectorAll('div');
const array = [...nodeList];
// 字符串转数组
[...'hello']; // ['h', 'e', 'l', 'l', 'o']5.2 对象展开
// 对象拷贝
const obj1 = { x: 1, y: 2 };
const obj2 = { ...obj1 };
// 对象合并(后者覆盖前者)
const defaults = { color: 'red', size: 'large' };
const options = { size: 'small' };
const config = { ...defaults, ...options };
// { color: 'red', size: 'small' }
// React状态更新
this.setState(prevState => ({
...prevState,
count: prevState.count + 1
}));5.3 剩余参数(Rest Parameters)
// 收集剩余参数
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4, 5); // 15
// 替代arguments对象
function oldWay() {
const args = Array.prototype.slice.call(arguments);
}
function newWay(...args) {
// args已经是真数组
}0x06 Promise与异步编程
6.1 Promise基础
// 创建Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('Success!');
} else {
reject(new Error('Failed!'));
}
}, 1000);
});
// 使用Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Done'));6.2 Promise链式调用
// 避免回调地狱
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(response => response.json())
.then(posts => {
console.log('User posts:', posts);
})
.catch(error => {
console.error('Error:', error);
});6.3 Promise静态方法
// Promise.all:并行执行,全部成功
Promise.all([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
]).then(([user, posts, comments]) => {
// 全部完成
});
// Promise.race:返回最快的
Promise.race([
fetch('/api/server1'),
fetch('/api/server2')
]).then(response => {
// 最快的服务器响应
});
// Promise.allSettled:等待全部完成(不管成功失败)
Promise.allSettled([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
]).then(results => {
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
});
// Promise.any:返回第一个成功的
Promise.any([
Promise.reject('error1'),
Promise.resolve(2),
Promise.resolve(3)
]).then(result => {
// 2(第一个成功的)
});6.4 async/await(ES2017)
// Promise方式
function getUser() {
return fetch('/api/user')
.then(res => res.json())
.then(user => {
return fetch(`/api/posts?userId=${user.id}`)
.then(res => res.json());
});
}
// async/await方式(更清晰)
async function getUser() {
const userRes = await fetch('/api/user');
const user = await userRes.json();
const postsRes = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsRes.json();
return posts;
}
// 错误处理
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}
// 并行执行
async function fetchAll() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { users, posts };
}0x07 类(Class):面向对象编程
7.1 类的定义
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
console.log(`Hello, I'm ${this.name}`);
}
// Getter
get description() {
return `${this.name} (${this.age} years old)`;
}
// Setter
set age(value) {
if (value < 0) throw new Error('Age must be positive');
this._age = value;
}
// 静态方法
static create(name, age) {
return new Person(name, age);
}
}
// 使用
const person = new Person('Alice', 30);
person.greet();7.2 继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks`);
}
}
const dog = new Dog('Rex', 'Labrador');
dog.speak();
// "Rex makes a sound"
// "Rex barks"7.3 私有属性(ES2022)
class BankAccount {
#balance = 0; // 私有属性
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.#balance); // SyntaxError: 私有属性不可访问0x08 模块化(Modules)
8.1 导出(Export)
// export.js
// 命名导出
export const PI = 3.14159;
export function square(x) {
return x * x;
}
export class Circle {
constructor(radius) {
this.radius = radius;
}
}
// 批量导出
const name = 'Alice';
const age = 30;
export { name, age };
// 重命名导出
export { name as userName, age as userAge };
// 默认导出(一个模块只能有一个)
export default function() {
console.log('Default export');
}8.2 导入(Import)
// 命名导入
import { PI, square } from './math.js';
// 重命名导入
import { name as userName } from './user.js';
// 导入全部
import * as math from './math.js';
console.log(math.PI, math.square(2));
// 默认导入
import myFunction from './module.js';
// 混合导入
import React, { useState, useEffect } from 'react';
// 动态导入(ES2020)
const module = await import('./module.js');
// 或
import('./module.js').then(module => {
module.doSomething();
});8.3 模块的特性
// 1. 模块自动使用严格模式
// 无需 'use strict';
// 2. 模块顶层this是undefined
console.log(this); // undefined(不是window)
// 3. 模块只执行一次
// utils.js
console.log('Module loaded');
// main.js
import './utils.js'; // "Module loaded"
import './utils.js'; // 不会再次输出0x09 增强的对象字面量
9.1 属性简写
const name = 'Alice';
const age = 30;
// ES5
const person = {
name: name,
age: age
};
// ES6
const person = { name, age };9.2 方法简写
// ES5
const obj = {
greet: function() {
console.log('Hello');
}
};
// ES6
const obj = {
greet() {
console.log('Hello');
}
};9.3 计算属性名
const prop = 'foo';
const obj = {
[prop]: 'bar',
['key' + 42]: 'value',
[Symbol.iterator]: function*() {
yield 1;
yield 2;
}
};
console.log(obj.foo); // 'bar'
console.log(obj.key42); // 'value'0x10 新增的数据结构
10.1 Set:唯一值集合
// 创建Set
const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) {1, 2, 3}
// 添加元素
set.add(4);
// 删除元素
set.delete(2);
// 检查存在
set.has(1); // true
// 大小
set.size; // 3
// 遍历
for (const value of set) {
console.log(value);
}
// 数组去重
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)]; // [1, 2, 3]10.2 Map:键值对集合
// 创建Map
const map = new Map();
// 设置值
map.set('name', 'Alice');
map.set('age', 30);
// 对象作为键
const obj = {};
map.set(obj, 'value');
// 获取值
map.get('name'); // 'Alice'
// 删除
map.delete('age');
// 检查存在
map.has('name'); // true
// 大小
map.size; // 2
// 遍历
for (const [key, value] of map) {
console.log(`${key} = ${value}`);
}
// Map vs Object
const objMap = {
'[object Object]': 'value' // 对象键会被转为字符串
};
const realMap = new Map();
realMap.set({}, 'value'); // 对象作为键10.3 WeakSet与WeakMap
// WeakSet:弱引用集合,元素只能是对象
const weakSet = new WeakSet();
let obj = { x: 1 };
weakSet.add(obj);
obj = null; // 对象可被垃圾回收
// WeakMap:弱引用键值对
const weakMap = new WeakMap();
let key = { id: 1 };
weakMap.set(key, 'value');
key = null; // 键可被垃圾回收
// 应用:私有数据存储
const privateData = new WeakMap();
class Person {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}0x11 Symbol:唯一标识符
// 创建Symbol
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');
console.log(sym2 === sym3); // false(每个Symbol都是唯一的)
// 对象属性
const obj = {
[Symbol('privateProperty')]: 'secret'
};
// Symbol不会被常规遍历
Object.keys(obj); // []
Object.getOwnPropertySymbols(obj); // [Symbol(privateProperty)]
// 内置Symbol
class MyArray extends Array {
// 自定义迭代行为
*[Symbol.iterator]() {
for (let i = this.length - 1; i >= 0; i--) {
yield this[i];
}
}
}
const arr = new MyArray(1, 2, 3);
[...arr]; // [3, 2, 1](反向迭代)0x12 迭代器与生成器
12.1 迭代器协议
// 自定义迭代器
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}12.2 生成器函数
// 生成器函数
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// 斐波那契数列
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
// 异步生成器
async function* asyncGenerator() {
for (let i = 1; i <= 3; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
for await (const num of asyncGenerator()) {
console.log(num); // 1秒后输出1,再1秒后输出2...
}
})();0x13 总结:ES6+的影响
13.1 核心特性速查表
| 特性 | 用途 | 替代方案 |
|---|---|---|
| let/const | 块级作用域 | var |
| 箭头函数 | 简洁语法、词法this | function |
| 解构赋值 | 数据提取 | 手动赋值 |
| 模板字符串 | 字符串拼接 | • 连接 |
| 展开运算符 | 数组/对象操作 | concat/Object.assign |
| Promise | 异步编程 | 回调函数 |
| Class | 面向对象 | 原型继承 |
| Module | 模块化 | CommonJS/AMD |
13.2 浏览器兼容性
// Babel转译配置
// .babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 11"]
},
"useBuiltIns": "usage",
"corejs": 3
}]
]
}13.3 最佳实践
- 优先使用const:避免意外修改
- 善用解构:提高代码可读性
- 使用async/await:替代Promise链
- 模块化开发:保持代码组织性
- 利用Map/Set:替代Object在特定场景
- 箭头函数注意this:理解词法绑定
记住:ES6+不是语法糖,而是编程范式的升级。
参考资料: