返回博客
技术 2025年3月4日 9 分钟阅读 · 1953 字

JavaScript ES6+ 核心特性详解:从 let/const 到 Proxy

全面解析 ES6 至 ES14 的重要特性,掌握现代 JavaScript 的核心能力

#JavaScript #ES6 #语法特性
本文由 AI 辅助生成,经人工审核发布

ES6(ES2015)是 JavaScript 历史上最重要的更新,此后每年发布一个版本。本文梳理从 ES6 到 ES14 的核心特性,帮助开发者系统掌握现代 JavaScript 的全貌。

一、let 与 const:块级作用域

1.1 三种变量声明

var a = 1;    // 函数作用域,存在变量提升
let b = 2;    // 块级作用域,无提升
const c = 3;  // 块级作用域,不可重新赋值
特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升是(值为 undefined)否(TDZ)否(TDZ)
可重新赋值
可重新声明
挂载到 window

1.2 暂时性死区(TDZ)

console.log(x); // ReferenceError
let x = 10;

letconst 声明的变量在声明前不可访问,这被称为暂时性死区(Temporal Dead Zone)。

1.3 const 的注意事项

const obj = { a: 1 };
obj.a = 2;        // 合法:const 不冻结对象属性
obj.b = 3;        // 合法
// obj = {};      // TypeError:不能重新赋值

// 如需完全冻结
const frozen = Object.freeze({ a: 1 });
frozen.a = 2;     // 严格模式下报错,非严格模式静默失败

二、箭头函数

2.1 语法

// 基本语法
const add = (a, b) => a + b;

// 等价于
const add = function(a, b) {
  return a + b;
};

// 单参数可省略括号
const square = x => x * x;

// 无参数需空括号
const greet = () => 'Hello';

// 多行函数体需 return
const compute = (a, b) => {
  const sum = a + b;
  return sum * 2;
};

// 返回对象需括号
const makeUser = (name, age) => ({ name, age });

2.2 this 绑定

箭头函数不绑定自己的 this,而是继承外层作用域的 this

function Timer() {
  this.seconds = 0;

  // 普通函数:this 指向调用者(setInterval 的回调),丢失了 Timer 实例
  setInterval(function() {
    this.seconds++; // NaN
  }, 1000);

  // 箭头函数:继承外层 this(Timer 实例)
  setInterval(() => {
    this.seconds++; // 正确递增
  }, 1000);
}

2.3 不适用场景

// 1. 对象方法(this 不指向对象)
const obj = {
  value: 42,
  getValue: () => this.value  // this 指向外层,不是 obj
};

// 2. 需要绑定 this 的回调(如事件监听器中需要 this 指向元素)
button.addEventListener('click', function() {
  this.classList.toggle('active');  // this 指向 button
});

// 3. 构造函数(箭头函数不能 new)
const Foo = () => {};
new Foo(); // TypeError

三、解构赋值

3.1 数组解构

const [a, b, c] = [1, 2, 3];
// a=1, b=2, c=3

// 跳过元素
const [first, , third] = [1, 2, 3];
// first=1, third=3

// 默认值
const [x = 10, y = 20] = [5];
// x=5, y=20

// 剩余元素
const [head, ...tail] = [1, 2, 3, 4];
// head=1, tail=[2,3,4]

// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];
// a=2, b=1

3.2 对象解构

const user = { name: 'Alice', age: 30, role: 'admin' };

// 基本解构
const { name, age } = user;

// 重命名
const { name: userName } = user;

// 默认值
const { name, age, country = 'CN' } = user;

// 嵌套解构
const { name, address: { city } } = {
  name: 'Bob',
  address: { city: '北京' }
};

// 剩余属性
const { name, ...rest } = user;
// rest = { age: 30, role: 'admin' }

3.3 函数参数解构

// 配置项解构,带默认值
function createUser({ name, age = 18, role = 'user' } = {}) {
  return { name, age, role };
}

createUser({ name: 'Alice' });
// { name: 'Alice', age: 18, role: 'user' }

四、模板字符串

const name = 'World';

// 基本插值
const greeting = `Hello, ${name}!`;

// 多行字符串
const html = `
  <div>
    <h1>${name}</h1>
  </div>
`;

// 表达式
const price = 19.99;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;

// 嵌套模板
const items = ['a', 'b', 'c'];
const list = `
  <ul>
    ${items.map(item => `<li>${item}</li>`).join('')}
  </ul>
`;

// 标签模板
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
  }, '');
}

const keyword = 'CSS';
highlight`搜索关键词 ${keyword} 已高亮`;

五、Promise 与 async/await

5.1 Promise 基础

// 创建 Promise
const fetchUser = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: 'Alice' });
      } else {
        reject(new Error('无效的 ID'));
      }
    }, 1000);
  });
};

// 消费 Promise
fetchUser(1)
  .then(user => console.log(user))
  .catch(err => console.error(err))
  .finally(() => console.log('完成'));

5.2 Promise 组合

// 全部成功才成功
Promise.all([fetchUser(1), fetchUser(2)])
  .then(([user1, user2]) => console.log(user1, user2));

// 全部完成(无论成功失败)
Promise.allSettled([fetchUser(1), fetchUser(-1)])
  .then(results => {
    results.forEach(r => {
      console.log(r.status); // 'fulfilled' 或 'rejected'
    });
  });

// 最快返回的(成功或失败)
Promise.race([fetchUser(1), timeout(500)])
  .then(result => console.log(result));

// 最快成功的(忽略失败)
Promise.any([fetchUser(-1), fetchUser(1)])
  .then(user => console.log(user));
方法成功条件失败条件
Promise.all全部成功任一失败
Promise.allSettled不会失败-
Promise.race最快的成功最快的失败
Promise.any任一成功全部失败

5.3 async/await

async function getUserData(id) {
  try {
    const user = await fetchUser(id);
    const posts = await fetchPosts(user.id);
    return { user, posts };
  } catch (err) {
    console.error('获取数据失败:', err);
    throw err;
  }
}

5.4 并发优化

// 串行(慢)
async function serial() {
  const user = await fetchUser(1);
  const settings = await fetchSettings();
  return { user, settings };
}

// 并行(快)
async function parallel() {
  const [user, settings] = await Promise.all([
    fetchUser(1),
    fetchSettings()
  ]);
  return { user, settings };
}

六、Symbol、Map 与 Set

6.1 Symbol

// 唯一标识符
const s1 = Symbol('id');
const s2 = Symbol('id');
s1 === s2; // false

// 作为对象 key(不会被 Object.keys 遍历)
const ID = Symbol('id');
const obj = { [ID]: 123, name: 'Alice' };
Object.keys(obj);       // ['name']
Object.getOwnPropertySymbols(obj); // [Symbol(id)]

// 全局 Symbol 注册表
const globalSym = Symbol.for('shared');
Symbol.for('shared') === globalSym; // true

6.2 Map 与 WeakMap

// Map:键可以是任意类型,保持插入顺序
const map = new Map();
map.set('name', 'Alice');
map.set(42, 'answer');
map.set({ obj: true }, '对象键');

map.get('name');   // 'Alice'
map.has(42);       // true
map.size;          // 3
map.delete(42);
map.forEach((value, key) => console.log(key, value));

// WeakMap:键必须是对象,弱引用,不可遍历
const weakMap = new WeakMap();
let key = {};
weakMap.set(key, 'data');
key = null; // weakMap 中的条目会被垃圾回收

6.3 Set 与 WeakSet

// Set:值的集合,自动去重
const set = new Set([1, 2, 3, 3, 2]);
set.size;          // 3
set.add(4);
set.has(3);        // true
set.delete(1);

// 数组去重
const unique = [...new Set([1, 1, 2, 3, 3])];

// WeakSet:只能存对象,弱引用
const weakSet = new WeakSet();
weakSet.add({});

七、Proxy 与 Reflect

7.1 Proxy 代理

const handler = {
  get(target, prop, receiver) {
    console.log(`读取 ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`设置 ${prop} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
};

const data = new Proxy({ count: 0 }, handler);
data.count;        // 打印 "读取 count"
data.count = 5;    // 打印 "设置 count = 5"

7.2 实用场景:数据验证

function createValidator(target, rules) {
  return new Proxy(target, {
    set(obj, prop, value) {
      const rule = rules[prop];
      if (rule && !rule.validate(value)) {
        throw new Error(rule.message);
      }
      obj[prop] = value;
      return true;
    }
  });
}

const user = createValidator({}, {
  age: {
    validate: v => typeof v === 'number' && v >= 0 && v <= 150,
    message: '年龄必须在 0-150 之间'
  }
});

user.age = 25;   // 正常
user.age = -1;   // 抛出错误

八、可选链与空值合并

8.1 可选链 ?.

const user = { profile: { name: 'Alice' } };

// 旧写法
const name = user && user.profile && user.profile.name;

// 可选链
const name = user?.profile?.name;

// 函数调用
const result = obj.method?.();

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

8.2 空值合并 ??

// ?? 只在 null/undefined 时取默认值
const count = response.count ?? 0;

// 对比 || 的陷阱
0 || 'default';     // 'default'(0 是 falsy)
0 ?? 'default';     // 0(0 不是 null/undefined)

'' || 'default';    // 'default'
'' ?? 'default';    // ''

false || 'default'; // 'default'
false ?? 'default'; // false

九、类字段与私有属性

9.1 类字段

class Counter {
  // 公有字段
  count = 0;
  name = '默认计数器';

  // 私有字段(ES2022)
  #id = Math.random();

  // 静态字段
  static instanceCount = 0;

  constructor(name) {
    if (name) this.name = name;
    Counter.instanceCount++;
  }

  // 私有方法
  #log() {
    console.log(`[${this.#id}] count = ${this.count}`);
  }

  increment() {
    this.count++;
    this.#log();
    return this.count;
  }

  // 静态方法
  static getInstanceCount() {
    return Counter.instanceCount;
  }
}

9.2 getter/setter

class Temperature {
  #celsius = 0;

  get celsius() { return this.#celsius; }
  set celsius(value) {
    if (value < -273.15) throw new Error('低于绝对零度');
    this.#celsius = value;
  }

  get fahrenheit() { return this.#celsius * 9/5 + 32; }
  set fahrenheit(value) { this.#celsius = (value - 32) * 5/9; }
}

十、顶层 await

ES2022 引入顶层 await,允许在模块顶层直接使用 await,无需包裹在 async 函数中:

// config.js
const response = await fetch('/api/config');
export const config = await response.json();

// main.js
import { config } from './config.js';
console.log(config); // config 已加载完毕才执行

应用场景

// 动态导入依赖
const heavyModule = await import('./heavy-feature.js');

// 初始化数据库连接
const db = await connectDatabase();
export { db };

注意:顶层 await 会阻塞依赖该模块的其他模块加载,仅在 ES Module 中可用。

十一、其他重要特性速览

特性版本示例
展开运算符ES2015[...arr1, ...arr2] {...obj1, ...obj2}
默认参数ES2015function f(a = 1) {}
for...ofES2015for (const item of iterable) {}
Array.flatES2019[1, [2, [3]]].flat(Infinity)
Array.flatMapES2019arr.flatMap(x => [x, x*2])
Object.fromEntriesES2019Object.fromEntries(entries)
String.replaceAllES2021'a-b'.replaceAll('-', '_')
Promise.allSettledES2020见上文
?? ?.ES2020见上文
Array.atES2022arr.at(-1) 取最后一个
Object.hasOwnES2022Object.hasOwn(obj, 'key')
数组分组ES2024Object.groupBy(arr, fn)

总结

现代 JavaScript 从 ES6 开始脱胎换骨,每年稳步进化。掌握这些特性的关键不是死记语法,而是理解它们解决的问题:

  • let/const 解决了作用域和不可变性
  • 箭头函数解决了 this 绑定和代码简洁性
  • 解构和展开简化了数据操作
  • Promise 和 async/await 重塑了异步编程
  • Symbol/Map/Set 填补了数据结构的空白
  • Proxy/Reflect 提供了元编程能力
  • 私有字段和顶层 await 完善了模块化和封装

善用这些特性,编写出更简洁、更健壮、更易维护的 JavaScript 代码。