Skip to content

ES6+ 特性

本文介绍ECMAScript 2015及后续版本引入的新特性,包括箭头函数、解构赋值、模板字符串、类、模块系统等。

let 和 const

ES6引入了块级作用域变量声明关键字 letconst,解决了 var 的一些问题。

javascript
// let - 块级作用域变量
let x = 10;
if (true) {
  let x = 20; // 这是一个新的变量,仅在此块中有效
  console.log(x); // 20
}
console.log(x); // 10

// const - 常量声明
const PI = 3.14159;
// PI = 3.14; // 错误:不能重新赋值

// 但对于对象和数组,const只保证引用不变,内容可以修改
const person = { name: "John" };
person.name = "Jane"; // 这是允许的
// person = {}; // 错误:不能重新赋值

箭头函数

箭头函数提供了更简洁的函数语法,并且不绑定自己的 this

javascript
// 传统函数表达式
let add = function(a, b) {
  return a + b;
};

// 箭头函数
let addArrow = (a, b) => a + b;

// 多行箭头函数需要使用花括号和return
let multiply = (a, b) => {
  let result = a * b;
  return result;
};

// 没有参数的箭头函数
let sayHello = () => "Hello!";

// 单个参数可以省略括号
let square = x => x * x;

// this绑定的区别
let counter = {
  count: 0,
  // 传统函数:this绑定到调用对象
  incrementTraditional: function() {
    setTimeout(function() {
      this.count++; // this指向window,不是counter
      console.log(this.count); // NaN
    }, 1000);
  },
  // 箭头函数:this继承自外部作用域
  incrementArrow: function() {
    setTimeout(() => {
      this.count++; // this指向counter
      console.log(this.count); // 1
    }, 1000);
  }
};

模板字符串

模板字符串提供了更强大的字符串插值和多行字符串功能。

javascript
// 传统字符串连接
let name = "Alice";
let greeting = "Hello, " + name + "!";

// 模板字符串
let greetingTemplate = `Hello, ${name}!`;

// 多行字符串
let multiline = `这是第一行
这是第二行
这是第三行`;

// 表达式插值
let a = 5;
let b = 10;
console.log(`${a} + ${b} = ${a + b}`);

// 带标签的模板字符串
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] ? `<strong>${values[i]}</strong>` : "");
  }, "");
}

let user = "John";
let status = "premium";
let message = highlight`用户 ${user} 拥有 ${status} 会员资格`;
// 结果: "用户 <strong>John</strong> 拥有 <strong>premium</strong> 会员资格"

解构赋值

解构赋值允许从数组或对象中提取值并赋给变量。

javascript
// 数组解构
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

// 跳过元素
let [x, , z] = [1, 2, 3];
console.log(x, z); // 1 3

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

// 默认值
let [p = 10, q = 20] = [1];
console.log(p, q); // 1 20

// 对象解构
let person = { name: "Alice", age: 25, job: "Engineer" };
let { name, age } = person;
console.log(name, age); // "Alice" 25

// 重命名
let { name: userName, age: userAge } = person;
console.log(userName, userAge); // "Alice" 25

// 默认值
let { name: n, salary = "未知" } = person;
console.log(n, salary); // "Alice" "未知"

// 嵌套解构
let company = {
  name: "Tech Corp",
  address: {
    city: "San Francisco",
    country: "USA"
  }
};
let { address: { city } } = company;
console.log(city); // "San Francisco"

// 函数参数解构
function printPersonInfo({ name, age, job = "未知" }) {
  console.log(`${name}, ${age}岁, ${job}`);
}
printPersonInfo(person); // "Alice, 25岁, Engineer"

默认参数

ES6允许为函数参数设置默认值。

javascript
// 传统方式
function greet(name) {
  name = name || "Guest";
  return "Hello, " + name;
}

// ES6默认参数
function greetES6(name = "Guest", time = "day") {
  return `Good ${time}, ${name}!`;
}

console.log(greetES6());          // "Good day, Guest!"
console.log(greetES6("Alice"));   // "Good day, Alice!"
console.log(greetES6("Bob", "morning")); // "Good morning, Bob!"

// 默认参数可以是表达式
function getDefaultName() {
  return "Anonymous";
}

function welcome(name = getDefaultName()) {
  return `Welcome, ${name}!`;
}

扩展运算符

扩展运算符(...)可以展开数组或对象。

javascript
// 数组展开
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

// 合并数组
let combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// 复制数组
let copy = [...arr1];

// 将字符串展开为字符数组
let chars = [..."Hello"];
console.log(chars); // ["H", "e", "l", "l", "o"]

// 在函数调用中使用
function sum(a, b, c) {
  return a + b + c;
}
let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

// 对象展开(ES2018)
let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, d: 4 };

// 合并对象
let mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 2, c: 3, d: 4 }

// 复制对象并修改某些属性
let modified = { ...obj1, b: 3, e: 5 };
console.log(modified); // { a: 1, b: 3, e: 5 }

ES6引入了类语法,使面向对象编程更加直观。

javascript
// 类声明
class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 实例方法
  sayHello() {
    return `Hello, my name is ${this.name}`;
  }
  
  // 获取器
  get profile() {
    return `${this.name}, ${this.age} years old`;
  }
  
  // 设置器
  set profile(value) {
    [this.name, this.age] = value.split(", ");
  }
  
  // 静态方法
  static isAdult(age) {
    return age >= 18;
  }
}

// 创建实例
let alice = new Person("Alice", 25);
console.log(alice.sayHello()); // "Hello, my name is Alice"
console.log(alice.profile);     // "Alice, 25 years old"

// 调用静态方法
console.log(Person.isAdult(20)); // true

// 继承
class Employee extends Person {
  constructor(name, age, company) {
    // 调用父类构造函数
    super(name, age);
    this.company = company;
  }
  
  // 重写父类方法
  sayHello() {
    return `${super.sayHello()} and I work at ${this.company}`;
  }
  
  // 新方法
  getCompany() {
    return this.company;
  }
}

let bob = new Employee("Bob", 30, "Tech Corp");
console.log(bob.sayHello()); // "Hello, my name is Bob and I work at Tech Corp"

模块系统

ES6引入了标准化的模块系统,用于导入和导出功能。

javascript
// 导出变量、函数或类 (math.js)
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

export class Calculator {
  add(a, b) {
    return a + b;
  }
}

// 默认导出
export default function subtract(a, b) {
  return a - b;
}

// 导入 (app.js)
// 导入特定项
import { add, multiply, PI } from './math.js';

// 导入并重命名
import { add as sum, multiply as product } from './math.js';

// 导入默认导出
import subtract from './math.js';

// 导入所有内容
import * as math from './math.js';
console.log(math.PI);        // 3.14159
console.log(math.add(2, 3)); // 5

Promise

Promise提供了更优雅的异步编程方式。

javascript
// 创建Promise
let promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    let success = true;
    if (success) {
      resolve("操作成功");
    } else {
      reject("操作失败");
    }
  }, 1000);
});

// 使用Promise
promise
  .then(result => {
    console.log(result); // "操作成功"
    return "下一步";
  })
  .then(nextResult => {
    console.log(nextResult); // "下一步"
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    console.log("无论成功或失败都会执行");
  });

// Promise.all - 等待所有Promise完成
let promise1 = Promise.resolve(1);
let promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000));
let promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // [1, 2, 3]
  });

// Promise.race - 返回最先完成的Promise结果
Promise.race([promise1, promise2, promise3])
  .then(value => {
    console.log(value); // 1 (最快完成的)
  });

async/await

ES2017引入的async/await语法使异步代码更易读。

javascript
// 异步函数
async function fetchData() {
  try {
    // 等待Promise完成
    let response = await fetch('https://api.example.com/data');
    let data = await response.json();
    return data;
  } catch (error) {
    console.error("获取数据失败:", error);
    throw error;
  }
}

// 调用异步函数
async function processData() {
  try {
    console.log("开始获取数据...");
    let data = await fetchData();
    console.log("数据获取成功:", data);
    
    // 可以继续使用await处理其他异步操作
    await saveToDatabase(data);
    console.log("数据保存成功");
  } catch (error) {
    console.error("处理数据失败:", error);
  }
}

// 异步函数总是返回Promise
fetchData().then(data => {
  console.log("通过Promise处理结果:", data);
});

// 并行执行多个异步操作
async function fetchMultipleData() {
  // 同时启动多个异步操作
  let promise1 = fetch('https://api.example.com/data1');
  let promise2 = fetch('https://api.example.com/data2');
  
  // 等待所有操作完成
  let [response1, response2] = await Promise.all([promise1, promise2]);
  
  // 处理结果
  let data1 = await response1.json();
  let data2 = await response2.json();
  
  return [data1, data2];
}

其他ES6+特性

Map和Set

javascript
// Map - 键值对集合,任何值都可以作为键
let map = new Map();
map.set('key1', 'value1');
map.set(42, 'value2');
map.set({}, 'value3');

console.log(map.get('key1')); // "value1"
console.log(map.size);        // 3
console.log(map.has(42));     // true

map.delete(42);
map.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});

// Set - 唯一值的集合
let set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3 (重复值被忽略)
console.log(set.has(2)); // true

set.add(4);
set.delete(1);

for (let value of set) {
  console.log(value); // 2, 3, 4
}

Symbol

javascript
// 创建唯一标识符
let sym1 = Symbol();
let sym2 = Symbol('description');
let sym3 = Symbol('description');

console.log(sym2 === sym3); // false,即使描述相同

// 用作对象属性键
let obj = {};
obj[sym1] = 'value1';
obj[sym2] = 'value2';

console.log(obj[sym1]); // "value1"
console.log(Object.keys(obj)); // [] (Symbol不出现在常规枚举中)

// 获取对象的Symbol属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol(description)]

// 全局Symbol注册表
let globalSym1 = Symbol.for('global');
let globalSym2 = Symbol.for('global');

console.log(globalSym1 === globalSym2); // true
console.log(Symbol.keyFor(globalSym1)); // "global"

可选链操作符(ES2020)

javascript
let user = {
  name: "Alice",
  address: {
    city: "New York"
  }
};

// 传统方式
let city = user && user.address && user.address.city;

// 使用可选链
let cityOptional = user?.address?.city;
console.log(cityOptional); // "New York"

let zipCode = user?.address?.zipCode;
console.log(zipCode); // undefined (不会报错)

// 函数调用
let result = user.getDetails?.(); // 如果方法不存在,不会报错

空值合并操作符(ES2020)

javascript
// 传统方式
let name = user.name !== undefined && user.name !== null ? user.name : "Anonymous";

// 使用空值合并操作符
let nameNullish = user.name ?? "Anonymous";
console.log(nameNullish); // "Alice"

let username = user.username ?? "Anonymous";
console.log(username); // "Anonymous"

// 与逻辑或的区别
let count = 0;
let result1 = count || 10; // 0被视为假值,返回10
let result2 = count ?? 10; // 0不是null或undefined,返回0

私有类字段(ES2022)

javascript
class Person {
  // 公共字段
  name;
  
  // 私有字段(以#开头)
  #age;
  
  constructor(name, age) {
    this.name = name;
    this.#age = age;
  }
  
  // 公共方法
  getProfile() {
    return `${this.name}, ${this.#getAgeDescription()}`;
  }
  
  // 私有方法
  #getAgeDescription() {
    return this.#age < 18 ? "未成年" : "成年人";
  }
}

let person = new Person("Alice", 25);
console.log(person.name); // "Alice"
// console.log(person.#age); // 错误:私有字段不可访问
console.log(person.getProfile()); // "Alice, 成年人"