主题
第 06 章 · ES6+ 新特性大盘点
一句话开篇:ES6(2015)以及之后每年发布的新特性,让 JavaScript 从一门"凑合用"的脚本语言变成了真正"好用"的现代语言。本章把日常用到 80% 的语法点一网打尽。
0. 生活类比(先建立直觉)
把 ES5 想象成"老式手动挡",ES6+ 是"自动挡新车"——能开的人都开过老的,但开过新的就回不去了。
| 老(ES5) | 新(ES6+) | 类比 |
|---|---|---|
var | let / const | 房间没门 → 房间有门 |
function() {} | () => {} | 长袍马褂 → 简洁 T 恤 |
'a' + name + 'b' | `a${name}b` | 用胶水粘字符串 → 直接拼图 |
obj.a, obj.b | const {a, b} = obj | 一颗一颗剥豆 → 一抓一把豆 |
arr.concat(arr2) | [...arr, ...arr2] | 用胶布拼木板 → 直接拼乐高 |
| 原型继承一大坨 | class extends | 自己拼宜家家具 → 整套打包 |
| 自己造轮子的对象 | Map / Set | 路边小摊 → 正规超市 |
1. let / const(块级作用域)
js
{
let x = 1;
const y = 2;
var z = 3;
}
console.log(z); // 3
console.log(x); // ReferenceError| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数 | 块级 | 块级 |
| 变量提升 | ✅ undefined | ✅ TDZ | ✅ TDZ |
| 重复声明 | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ | ✅ | ❌ |
💡
const锁的是"引用",不锁内部内容:const arr = []; arr.push(1)是合法的。
2. 模板字符串(Template Literal)
用反引号 `,支持:
- 变量插值
${...} - 多行字符串
- 标签模板(高阶用法)
js
const name = 'Tom';
const age = 18;
// 普通插值
const s1 = `Hello, ${name}, you are ${age} years old.`;
// 多行
const s2 = `第一行
第二行
第三行`;
// 表达式
const s3 = `${age >= 18 ? '成年' : '未成年'}`;
// 标签模板
function tag(strings, ...values) {
return strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
}
const s4 = tag`Hello, ${name}, age ${age}`;3. 解构赋值(Destructuring)
3.1 数组解构
js
const [a, b, c] = [1, 2, 3]; // a=1, b=2, c=3
const [x, , z] = [1, 2, 3]; // x=1, z=3 跳过中间
const [first, ...rest] = [1, 2, 3, 4]; // first=1, rest=[2,3,4]
const [p = 10, q = 20] = [1]; // p=1, q=20 默认值3.2 对象解构
js
const { name, age } = { name: 'Tom', age: 18 };
// 重命名
const { name: userName } = { name: 'Tom' }; // userName = 'Tom'
// 默认值
const { city = 'Beijing' } = {};
// 嵌套
const { address: { city2 } } = { address: { city2: 'BJ' } };
// 函数参数解构
function greet({ name, age = 0 }) { return `${name}, ${age}`; }
greet({ name: 'Tom' }); // Tom, 0⚠️ 已声明的变量再解构赋值要加括号:
({a, b} = obj),否则{}会被当语句块。
4. 扩展运算符 / 剩余参数(...)
4.1 扩展(spread):把可迭代对象"摊开"
js
// 数组
const a = [1, 2, 3];
const b = [...a, 4, 5]; // [1,2,3,4,5]
const c = [...a, ...[6, 7]]; // [1,2,3,6,7]
// 对象(ES2018)
const o1 = { a: 1, b: 2 };
const o2 = { ...o1, c: 3 }; // {a:1,b:2,c:3}
// 同名后者覆盖前者,常用于"覆盖默认配置"
const config = { ...defaults, ...userInput };
// 函数调用
Math.max(...[1, 2, 3]); // 34.2 剩余参数(rest):把多个参数"收"成数组
js
function sum(...nums) {
return nums.reduce((s, n) => s + n, 0);
}
sum(1, 2, 3, 4); // 10
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]
const { a, ...rest } = { a: 1, b: 2, c: 3 }; // rest={b:2,c:3}4.3 spread vs rest 一句话
spread 是"打散",rest 是"打包"。看位置:在赋值号右侧或函数实参里就是 spread;在函数形参或解构左侧就是 rest。
5. 箭头函数
js
const add = (a, b) => a + b;
const square = x => x * x;
const fn = () => ({ a: 1 }); // 返回对象要包小括号与普通函数的区别:见第 04 章。简言之:没自己的 this、arguments、prototype,不能 new。
6. 函数默认参数 / 命名参数
js
function greet(name = 'World', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
greet(); // Hello, World!
greet('Tom'); // Hello, Tom!
greet('Tom', 'Hi'); // Hi, Tom!
// 命名参数(用对象解构模拟)
function fetch({ url, method = 'GET', timeout = 3000 }) { /* ... */ }
fetch({ url: '/api', timeout: 5000 });7. Class(类)
js
class Animal {
static type = '生物'; // 静态属性
#secret = '私有'; // 私有字段(# 开头)
constructor(name) {
this.name = name;
}
// 实例方法(在 prototype 上)
eat() {
console.log(`${this.name} 吃东西`);
}
// 静态方法
static describe() {
return Animal.type;
}
// getter / setter
get displayName() { return `[${this.name}]`; }
set displayName(v) { this.name = v.replace(/[\[\]]/g, ''); }
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() { console.log(`${this.name} woof!`); }
}
const d = new Dog('旺财', 'Husky');
d.eat(); // 旺财 吃东西
d.bark(); // 旺财 woof!核心点:
- 本质是构造函数 + 原型链的语法糖
- 必须
new调用,否则报错 - 默认严格模式
static静态方法/属性挂在类本身#开头是私有字段(ES2022)extends继承,super调用父类
8. 模块化(ESM)
ES6 引入官方模块系统,取代 CommonJS / AMD。
8.1 命名导出 / 导入
js
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }
export class Circle {}
// main.js
import { PI, add } from './math.js';
import * as math from './math.js';
import { add as plus } from './math.js'; // 别名8.2 默认导出
js
// user.js
export default class User {}
// main.js
import User from './user.js'; // 不需要花括号8.3 动态 import(按需加载)
js
button.addEventListener('click', async () => {
const { Modal } = await import('./modal.js');
new Modal().show();
});8.4 ESM vs CommonJS
| 维度 | ESM (import) | CommonJS (require) |
|---|---|---|
| 加载时机 | 编译时静态分析 | 运行时动态加载 |
| 输出 | 引用(值会变) | 拷贝(值不变) |
| this | undefined | module.exports |
| 异步 | ✅ 原生支持 | ❌ 同步 |
| Tree-Shaking | ✅ | ❌ |
| 浏览器原生支持 | ✅(type="module") | ❌ |
9. Symbol(独一无二的值)
js
const s1 = Symbol('id');
const s2 = Symbol('id');
s1 === s2; // false(每个 Symbol 都是唯一的)
const obj = { [s1]: 'value1' };
console.log(obj[s1]); // value1
console.log(Object.keys(obj)); // [](Symbol 不会被普通方法枚举)
// 应用:常量、防止键冲突
const STATUS = {
PENDING: Symbol('pending'),
FULFILLED: Symbol('fulfilled')
};Well-known Symbols:内置的 Symbol,让对象具备特殊行为:
js
class Range {
constructor(start, end) { this.start = start; this.end = end; }
[Symbol.iterator]() {
let cur = this.start;
const end = this.end;
return {
next() {
return cur < end ? { value: cur++, done: false } : { value: undefined, done: true };
}
};
}
}
for (const n of new Range(1, 5)) console.log(n); // 1 2 3 410. Map / Set / WeakMap / WeakSet
10.1 Map:键值对集合,键可以是任意类型
js
const m = new Map();
m.set('a', 1);
m.set({}, 'obj key'); // 对象作为 key
m.set(true, 'bool');
m.get('a'); // 1
m.has('a'); // true
m.delete('a');
m.size;
m.forEach((v, k) => console.log(k, v));Map vs Object:
| 维度 | Map | Object |
|---|---|---|
| 键类型 | 任意 | 字符串 / Symbol |
| 顺序 | 插入顺序 | 整数键先排序 |
| 大小 | .size | 需 Object.keys().length |
| 默认原型 | 无 | 有 Object.prototype |
| 性能(频繁增删) | 更优 | 一般 |
10.2 Set:无重复值集合
js
const s = new Set([1, 2, 2, 3, 3, 3]);
console.log(s.size); // 3
[...s]; // [1, 2, 3]
// 数组去重
const arr = [1, 2, 2, 3];
const unique = [...new Set(arr)]; // [1, 2, 3]10.3 WeakMap / WeakSet
- 键必须是对象
- 弱引用,不阻止 GC(key 没人引用时自动回收)
- 不可遍历,没
.size - 适合缓存、关联数据
js
const cache = new WeakMap();
function getData(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = compute(obj);
cache.set(obj, result);
return result;
}
// obj 被销毁后,缓存自动清理11. 可选链(?.)与空值合并(??)
11.1 可选链 ?.(ES2020)
js
const user = null;
const name = user?.profile?.name; // undefined,不会报错
// 函数
fn?.(); // fn 是 null/undefined 不调用
arr?.[0]; // 数组元素
obj?.method?.();11.2 空值合并 ??
|| 把 0、''、false 都当 falsy;?? 只对 null 和 undefined 生效。
js
const a = 0;
a || 10; // 10(0 被认为 falsy)
a ?? 10; // 0(只对 null/undefined)
const port = config.port ?? 3000; // 推荐11.3 ??= / ||= / &&= 逻辑赋值(ES2021)
js
a ??= 1; // 等价于 a = a ?? 1
a ||= 1; // 等价于 a = a || 1
a &&= 1; // 等价于 a = a && 112. 迭代器与 for...of
任何实现了 [Symbol.iterator]() 的对象都是可迭代的。
js
const arr = ['a', 'b', 'c'];
for (const v of arr) console.log(v); // a b c
for (const [i, v] of arr.entries()) console.log(i, v);
for (const k of Object.keys({a:1, b:2})) console.log(k);
// for...in 遍历键(含原型链上的,慎用)
// for...of 遍历值(要求可迭代)13. Generator(生成器)
js
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: false }
g.next(); // { value: undefined, done: true }
// 实现自定义迭代
function* range(start, end) {
for (let i = start; i < end; i++) yield i;
}
[...range(1, 5)]; // [1, 2, 3, 4]💡 async/await 在底层就是基于 Generator + Promise 实现的(参考 Babel 转译产物)。
14. ⚠️ 易踩的坑
- 坑 1:解构赋值 undefined 才用默认值,
null不会 - 坑 2:const 不是真正的"不可变",引用类型内部可改(用
Object.freeze) - 坑 3:ESM 中
this是undefined,CommonJS 中是module.exports - 坑 4:箭头函数不能用
arguments,用...args - 坑 5:扩展运算符是浅拷贝:
{...obj}嵌套对象仍共享引用 - 坑 6:
for...in不要遍历数组,会拿到原型上的属性 - 坑 7:可选链不能与赋值结合:
obj?.a = 1报错 - 坑 8:
Symbol不能用new调用:new Symbol()报错 - 坑 9:浏览器原生 ESM 必须带文件扩展名:
import './foo.js'不能省.js - 坑 10:私有字段
#x无法通过字符串访问
15. 一句话总结
ES6+ 的核心思想 = "让代码写得更短、更安全、更接近表达式"——块级作用域防穿透,解构与扩展运算符消除模板代码,class/import 提供工程化基础,可选链/空值合并让健壮性大幅提升。
16. 延伸阅读
- 阮一峰《ECMAScript 6 入门》
- TC39:Proposals
- MDN:ECMAScript 2015 Support