JavaScript ES6+ 新特性

ES6(2015)是 JS 历史上最大的飞跃,引入了几十项新特性,基本塑造了"现代 JS"的样子。之后每年发布一版(ES2016~ES2024),持续小步迭代。前面章节我们已经接触过很多新特性,这一章做个系统汇总,确保你不会"用了却不知道叫什么"。

1. 解构赋值

解构是从对象/数组里批量挑字段的语法糖,代码更简洁、意图更明确:

// 解构:从对象/数组里"挑出"字段

// 对象解构
const user = { name: "小明", age: 20, city: "北京" };
const { name, age } = user;
console.log(name, age);    // 小明 20

// 重命名 + 默认值
const { name: userName, phone = "未填写" } = user;
console.log(userName, phone);  // 小明 未填写

// 嵌套解构
const { city: userCity } = user;

// 数组解构
const [first, second, third] = [10, 20, 30];
console.log(first, second, third);   // 10 20 30

// 跳过中间元素
const [a, , c] = [1, 2, 3];
console.log(a, c);                   // 1 3

// 剩余元素
const [head, ...rest] = [1, 2, 3, 4];
console.log(head, rest);             // 1 [2,3,4]

// 交换变量(经典用法)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y);    // 2 1

解构在函数参数里特别有用——可以模拟命名参数,默认值也清晰:

// 函数参数解构(常用)
function render({ title, content = "默认内容", theme = "light" }) {
  console.log(title, content, theme);
}
render({ title: "标题" });
// 标题 默认内容 light

// React 组件 props 解构
function UserCard({ name, age, avatar = "/default.png" }) {
  return `${name} ${age}`;
}

2. 展开运算符 ...

... 是现代 JS 里出现频率最高的运算符之一,既能"展开"也能"收集":

// 展开运算符 ...:把数组/对象"展开"

// 1. 数组展开
const arr = [1, 2, 3];
const newArr = [...arr, 4, 5];
console.log(newArr);   // [1,2,3,4,5]

// 合并数组
const merged = [...[1, 2], ...[3, 4]];
console.log(merged);   // [1,2,3,4]

// 复制数组(浅拷贝)
const copy = [...arr];

// 把字符串拆成字符数组
const chars = [..."hello"];
console.log(chars);    // ["h","e","l","l","o"]

// 2. 对象展开
const user = { name: "小明", age: 20 };
const updated = { ...user, city: "北京", age: 21 };  // 后者覆盖前者
console.log(updated);
// { name: "小明", age: 21, city: "北京" }

// 合并对象
const defaults = { theme: "light", lang: "zh" };
const userPrefs = { theme: "dark" };
const settings = { ...defaults, ...userPrefs };
console.log(settings);    // { theme: "dark", lang: "zh" }

// 3. 函数参数:剩余参数
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3, 4));    // 10

展开是浅拷贝,嵌套对象仍是引用——这点必须记住。配合解构,可以轻松实现"覆盖默认值"、"对象合并"等常见操作。

3. 可选链 ?. 与 空值合并 ??

这两个 ES2020 引入的运算符,让 JS 的"防御性编程"代码量减半:

// 可选链 ?.:安全访问深层属性
const user = {
  name: "小明",
  address: {
    city: "北京"
  }
};

// ❌ 老写法:层层判断
const zip1 = user && user.address && user.address.zip;
console.log(zip1);   // undefined

// ✅ 可选链:任何一层不存在就返回 undefined
const zip2 = user?.address?.zip;
console.log(zip2);   // undefined(不报错)

// 安全调用方法(方法不存在时返回 undefined 而非报错)
const result = obj?.method?.();

// 数组索引也支持
const first = arr?.[0];

// 注意:?. 只能"读取",不能"赋值"
// user?.address?.zip = "100000";  // ❌ 语法错误

// 空值合并 ??:左侧为 null/undefined 时取右侧
const name = userInput ?? "匿名";
// 注意和 || 的区别:
console.log(0 || 10);    // 10(0 是假值)
console.log(0 ?? 10);    // 0(0 不是 null/undefined)
console.log("" || "默认");   // "默认"
console.log("" ?? "默认");   // ""(空字符串不触发 ??)

记忆要点:

4. 模板字符串

const name = "小明";
const age = 20;

// 反引号 + ${} 嵌入变量
const greeting = `你好 ${name},${age} 岁`;

// 多行
const html = `
  <div>
    <h1>${name}</h1>
    <p>${age}</p>
  </div>
`;

// 高级:标签模板(用于 styled-components 等库)
const styled = (strings, ...values) => strings.join("");

现代 JS 几乎不再用 + 拼接字符串——能写模板字符串就写。React 的 JSX、styled-components 的样式,底层都是模板字符串。

5. Map 与 Set

// Map:键值对集合,键可以是任意类型(包括对象)
const map = new Map();
map.set("name", "小明");
map.set("age", 20);
map.set({ objKey: true }, "对象作为键");

console.log(map.get("name"));   // 小明
console.log(map.has("name"));   // true
console.log(map.size);          // 3
map.delete("age");
map.clear();

// 遍历 Map(顺序是插入顺序)
for (const [key, value] of map) {
  console.log(key, value);
}

// Set:唯一值的集合(去重神器)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set);      // Set { 1, 2, 3 }(自动去重)
console.log(set.size); // 3
set.add(4);
set.has(2);            // true
set.delete(1);

// 数组去重(经典用法)
const dedup = [...new Set([1, 2, 2, 3, 3, 3])];
console.log(dedup);    // [1, 2, 3]

Map vs Object:Map 的键可以是任意类型(对象、数字),Object 的键只能是字符串/Symbol;Map 有 size 属性,顺序是插入顺序。需要"键值对"且键不是字符串时,用 Map。

Set 的经典用途:数组去重,一行代码 [...new Set(arr)]

6. 箭头函数

// 三种写法
const f1 = x => x * 2;             // 单参数 + 单表达式(最简)
const f2 = (a, b) => a + b;        // 多参数
const f3 = (a, b) => {             // 多语句
  const sum = a + b;
  return sum;                      // 需要 return
};

// 关键特性:没有自己的 this,继承外层
const obj = {
  name: "小明",
  greet() {
    [1, 2, 3].forEach(() => {
      console.log(this.name);      // "小明"(箭头函数继承 obj 的 this)
    });
  }
};

// 箭头函数 vs 普通函数
// 普通函数有自己的 this,容易丢
// 箭头函数没有 this,在回调里更安全
// 箭头函数没有 arguments,不能 new

7. 类 class

ES6 的 class原型链的语法糖——底层还是基于原型,但写法像 Java/C++,更直观:

// ES6 class:类的语法糖(本质还是原型链)
class Animal {
  // 构造函数
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  // 实例方法(原型方法)
  speak() {
    console.log(`${this.name} 叫:${this.sound}`);
  }

  // 静态方法(类方法)
  static create(name) {
    return new Animal(name, "...");   // 注:这是个示意
  }
}

const dog = new Animal("狗", "汪汪");
dog.speak();    // 狗 叫:汪汪
console.log(Animal.create("猫"));

// 继承:extends
class Cat extends Animal {
  constructor(name) {
    super(name, "喵喵");    // 必须先调 super
  }

  // 重写父类方法
  speak() {
    super.speak();          // 调用父类的 speak
    console.log(`${this.name} 舔了舔爪子`);
  }
}

const cat = new Cat("咪咪");
cat.speak();
// 咪咪 叫:喵喵
// 咪咪 舔了舔爪子

关键规则:

ES2022 私有字段:用 # 前缀定义真正的私有字段,外部访问不到。

class BankAccount {
  #balance = 0;     // 私有字段,外部访问不到
  
  deposit(amount) {
    this.#balance += amount;
  }
  
  get balance() {   // getter,像属性一样访问
    return this.#balance;
  }
}

const acc = new BankAccount();
acc.deposit(100);
console.log(acc.balance);     // 100(用 getter)
// console.log(acc.#balance); // SyntaxError

8. 模块系统(ES Module)

ES Module 是 JS 官方的模块系统,Node.js 和浏览器都支持。它让代码可以拆分文件、按需引用:

// ES Module:JavaScript 官方模块系统

// ---- math.js(导出) ----
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}
export default function multiply(a, b) {
  return a * b;
}

// ---- main.js(导入) ----
import multiply from "./math.js";          // 默认导入
import { PI, add } from "./math.js";       // 命名导入
import * as math from "./math.js";         // 全部导入

console.log(PI, add(1, 2), multiply(3, 4));

// 动态导入(返回 Promise,实现按需加载)
const module = await import("./math.js");
console.log(module.add(1, 2));

// 浏览器里使用:
// <script type="module" src="main.js"></script>
// 注意:模块默认严格、默认延迟加载(类似 defer)

命名导出 vs 默认导出:

新项目推荐统一用命名导出——重构、智能提示更友好,默认导出容易重命名混乱。

9. 其他常用 ES6+ 特性

// 1. Symbol:唯一的标识符
const s1 = Symbol("id");
const s2 = Symbol("id");
console.log(s1 === s2);    // false(每个 Symbol 都唯一)

// 2. 默认参数
const greet = (name = "匿名") => `你好,${name}`;
console.log(greet());      // 你好,匿名

// 3. Number.isFinite / Number.isInteger
console.log(Number.isInteger(3.14));   // false

// 4. Array.flat / flatMap
[[1, 2], [3, 4]].flat();        // [1,2,3,4]
[1, 2, 3].flatMap(x => [x, x * 2]); // [1,2,2,4,3,6]

// 5. Object.fromEntries:entries 的逆操作
Object.fromEntries([["a", 1], ["b", 2]]);  // { a: 1, b: 2 }

// 6. 数字分隔符(大数可读性)
const million = 1_000_000;

// 7. BigInt:超出安全整数的大数
const big = 9007199254740993n;
console.log(big + 1n);

// 8. globalThis:跨环境的全局对象(浏览器=window, Node=global)

10. Iterators 与 for-of

// 可迭代对象(Iterable):有 [Symbol.iterator] 方法
// 数组、字符串、Map、Set、arguments 都是

// for-of 遍历可迭代对象
for (const item of [1, 2, 3]) {
  console.log(item);
}

// 展开运算符 ... 也基于迭代器
[..."abc"];   // ["a","b","c"]

// 自定义可迭代对象
const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { done: true };
      }
    };
  }
};

for (const n of range) console.log(n);   // 1 2 3 4 5

理解迭代器,你就能解释为什么 for-of 能遍历数组但不能遍历普通对象(对象默认不是可迭代对象)。

11. Generator 函数

// function*:生成器函数,可以"暂停"
function* counter() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = counter();
console.log(gen.next());   // { value: 1, done: false }
console.log(gen.next());   // { value: 2, done: false }
console.log(gen.next());   // { value: 3, done: false }
console.log(gen.next());   // { value: undefined, done: true }

// 用 for-of 遍历
for (const n of counter()) {
  console.log(n);   // 1 2 3
}

// 实战:生成无限序列(用 take 限制)
function* naturalNumbers() {
  let n = 1;
  while (true) yield n++;
}

Generator 是迭代器的"语法糖",在状态机、惰性序列、async/await 底层实现里都有应用。日常开发用得不多,但理解它有助于看懂框架源码。

小结

ES6+ 是"现代 JS"的标志,核心特性必学:

掌握这些,你就掌握了 95% 的现代 JS 代码。最后我们看一个看似简单但极其常用的格式——JSON

← 上一篇 JavaScript 异步与 Promise

下一篇 JavaScript JSON

✈️💬