【JavaScript】ES6+ 新特性

发布于:2025-04-13 ⋅ 阅读:(23) ⋅ 点赞:(0)

在这里插入图片描述

个人主页:Guiat
归属专栏:HTML CSS JavaScript

在这里插入图片描述

正文

1. ES6 (ECMAScript 2015) 核心特性

1.1 let 和 const

新的变量声明方式,提供块级作用域。

// let 声明的变量可以重新赋值
let count = 1;
count = 2;

// const 声明的常量不能重新赋值
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable

// 块级作用域
if (true) {
  let blockScoped = 'only available in this block';
  var functionScoped = 'available throughout function';
}
// console.log(blockScoped); // ReferenceError
console.log(functionScoped); // "available throughout function"

// 暂时性死区 (TDZ)
// console.log(notYetDeclared); // ReferenceError
let notYetDeclared = 'this variable is in the TDZ before declaration';

1.2 箭头函数

简洁的函数语法,没有自己的 thisargumentssupernew.target

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

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

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

// 无参数需要空括号
const getRandomNumber = () => Math.random();

// 多行函数体需要花括号和 return 语句
const calculateArea = (width, height) => {
  const area = width * height;
  return area;
};

// this 指向
function Traditional() {
  this.value = 1;
  setTimeout(function() {
    this.value++; // this 指向 window 或 undefined
    console.log(this.value); // NaN 或 错误
  }, 1000);
}

function ArrowFunction() {
  this.value = 1;
  setTimeout(() => {
    this.value++; // this 指向外层作用域的 this
    console.log(this.value); // 2
  }, 1000);
}

1.3 模板字符串

增强的字符串字面量,支持多行字符串和字符串插值。

// 多行字符串
const multiline = `This is a string
that spans multiple
lines.`;

// 字符串插值
const name = 'World';
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, World!"

// 表达式插值
const a = 5;
const b = 10;
console.log(`Sum: ${a + b}, Product: ${a * b}`);
// "Sum: 15, Product: 50"

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

const language = 'JavaScript';
const experience = 5;

const text = highlight`I have been programming in ${language} for ${experience} years.`;
// "I have been programming in <strong>JavaScript</strong> for <strong>5</strong> years."

1.4 解构赋值

从数组或对象中提取值,对变量进行赋值。

// 数组解构
const [a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

// 跳过某些值
const [x, , z] = [1, 2, 3];
console.log(x, z); // 1 3

// 剩余模式
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest); // [2, 3, 4]

// 默认值
const [p = 5, q = 7] = [1];
console.log(p, q); // 1 7

// 对象解构
const { name, age } = { name: 'John', age: 30 };
console.log(name); // "John"
console.log(age); // 30

// 属性重命名
const { name: userName, age: userAge } = { name: 'John', age: 30 };
console.log(userName); // "John"
console.log(userAge); // 30

// 嵌套解构
const {
  name,
  address: { city, country }
} = { name: 'John', address: { city: 'New York', country: 'USA' } };
console.log(city, country); // "New York" "USA"

// 函数参数解构
function printPerson({ name, age = 25 }) {
  console.log(`${name} is ${age} years old`);
}
printPerson({ name: 'Alice' }); // "Alice is 25 years old"

1.5 默认参数

函数参数的默认值。

// 基本用法
function greet(name = 'Guest') {
  return `Hello, ${name}!`;
}
console.log(greet()); // "Hello, Guest!"
console.log(greet('John')); // "Hello, John!"

// 表达式作为默认值
function getTimestamp(time = Date.now()) {
  return time;
}

// 参数默认值可以引用前面的参数
function createRange(start = 0, end = start + 10) {
  return Array.from({ length: end - start }, (_, i) => start + i);
}
console.log(createRange(5)); // [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

// 解构与默认参数结合
function fetchData({ url, method = 'GET', data = {} } = {}) {
  console.log(method, url, data);
}
fetchData({ url: 'api/users' }); // "GET" "api/users" {}
fetchData(); // "GET" undefined {}

1.6 扩展运算符

将可迭代对象展开为各个独立元素。

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

// 用于函数参数
function sum(a, b, c) {
  return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

// 合并数组
const firstHalf = [1, 2, 3];
const secondHalf = [4, 5, 6];
const fullArray = [...firstHalf, ...secondHalf];
console.log(fullArray); // [1, 2, 3, 4, 5, 6]

// 创建数组副本
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3]
console.log(copy); // [1, 2, 3, 4]

// 对象展开 (ES2018)
const obj1 = { x: 1, y: 2 };
const obj2 = { ...obj1, z: 3 };
console.log(obj2); // { x: 1, y: 2, z: 3 }

// 属性覆盖
const defaults = { theme: 'light', fontSize: 14 };
const userSettings = { theme: 'dark' };
const mergedSettings = { ...defaults, ...userSettings };
console.log(mergedSettings); // { theme: 'dark', fontSize: 14 }

1.7 类 (Classes)

JavaScript 面向对象编程的语法糖。

// 基本类声明
class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 实例方法
  greet() {
    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 createAnonymous() {
    return new Person('Anonymous', 0);
  }
}

const john = new Person('John', 30);
console.log(john.greet()); // "Hello, my name is John"
console.log(john.profile); // "John, 30 years old"

john.profile = 'Jane,25';
console.log(john.name); // "Jane"
console.log(john.age); // "25"

const anonymous = Person.createAnonymous();
console.log(anonymous.name); // "Anonymous"

// 类继承
class Employee extends Person {
  constructor(name, age, company) {
    // 调用父类构造函数
    super(name, age);
    this.company = company;
  }
  
  // 覆盖父类方法
  greet() {
    return `${super.greet()} and I work at ${this.company}`;
  }
  
  // 添加新方法
  work() {
    return `${this.name} is working`;
  }
}

const alice = new Employee('Alice', 28, 'Google');
console.log(alice.greet()); // "Hello, my name is Alice and I work at Google"
console.log(alice.work()); // "Alice is working"

1.8 模块 (Modules)

官方的 JavaScript 代码模块化解决方案。

// math.js - 导出模块
// 命名导出
export function add(a, b) {
  return a + b;
}

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

export const PI = 3.14159;

// 默认导出
export default class Calculator {
  static add(a, b) {
    return a + b;
  }
}

// app.js - 导入模块
// 导入命名导出
import { add, multiply, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159

// 导入默认导出
import Calculator from './math.js';
console.log(Calculator.add(2, 3)); // 5

// 导入全部并指定命名空间
import * as math from './math.js';
console.log(math.multiply(2, 3)); // 6
console.log(math.default.add(2, 3)); // 5

// 导入时重命名
import { add as sum, PI as pi } from './math.js';
console.log(sum(2, 3)); // 5
console.log(pi); // 3.14159

// 动态导入 (ES2020)
async function loadMath() {
  const math = await import('./math.js');
  return math.add(2, 3);
}

1.9 Promise

用于异步操作的对象,代表一个尚未完成但预期将来会完成的操作。

// 创建 Promise
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('Operation successful');
    } else {
      reject(new Error('Operation failed'));
    }
  }, 1000);
});

// 使用 Promise
promise
  .then(result => {
    console.log('Success:', result);
    return 'Next step';
  })
  .then(data => {
    console.log('Chained then:', data);
  })
  .catch(error => {
    console.error('Error:', error.message);
  })
  .finally(() => {
    console.log('Cleanup code (always runs)');
  });

// Promise.all - 等待所有 Promise 完成
Promise.all([
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
])
  .then(responses => console.log('All requests succeeded'))
  .catch(error => console.error('At least one request failed'));

// Promise.race - 完成或拒绝于第一个完成或拒绝的 Promise
Promise.race([
  fetch('/api/fast-endpoint'),
  new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
])
  .then(response => console.log('Response received in time'))
  .catch(error => console.error('Timed out or failed'));

1.10 生成器 (Generators)

可以暂停和恢复执行的函数。

// 基本生成器函数
function* countUp() {
  yield 1;
  yield 2;
  yield 3;
}

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

// 使用 for...of 遍历
for (const value of countUp()) {
  console.log(value); // 1, 2, 3
}

// 接收参数
function* dialogue() {
  const response1 = yield 'What is your name?';
  const response2 = yield `Hello, ${response1}! How old are you?`;
  yield `${response1} is ${response2} years old.`;
}

const conversation = dialogue();
console.log(conversation.next().value); // "What is your name?"
console.log(conversation.next('John').value); // "Hello, John! How old are you?"
console.log(conversation.next('30').value); // "John is 30 years old."

// 无限序列
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5

2. ES2016 (ES7) 新特性

2.1 指数运算符

// 新的 ** 运算符
console.log(2 ** 3); // 8

// 等价于 Math.pow()
console.log(Math.pow(2, 3)); // 8

// 结合赋值运算符
let num = 3;
num **= 2;
console.log(num); // 9

2.2 Array.prototype.includes()

检查数组是否包含指定元素。

const array = [1, 2, 3, NaN, 4, 5];

// 使用 includes()
console.log(array.includes(3)); // true
console.log(array.includes(6)); // false

// 可以找到 NaN (与 indexOf 不同)
console.log(array.includes(NaN)); // true
console.log(array.indexOf(NaN) !== -1); // false

// 指定起始索引
console.log(array.includes(1, 2)); // false

3. ES2017 (ES8) 新特性

3.1 async/await

异步编程的语法糖,使异步代码更易于阅读和维护。

// 使用 Promise
function fetchUserWithPromise() {
  return fetch('/api/users/1')
    .then(response => response.json())
    .then(user => {
      return fetch(`/api/posts?userId=${user.id}`);
    })
    .then(response => response.json())
    .then(posts => {
      return { user, posts };
    })
    .catch(error => {
      console.error('Error:', error);
    });
}

// 使用 async/await
async function fetchUserWithAsync() {
  try {
    const userResponse = await fetch('/api/users/1');
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
    const posts = await postsResponse.json();
    
    return { user, posts };
  } catch (error) {
    console.error('Error:', error);
  }
}

// 并行请求
async function fetchDataParallel() {
  try {
    const [userResponse, settingsResponse] = await Promise.all([
      fetch('/api/users/1'),
      fetch('/api/settings')
    ]);
    
    const user = await userResponse.json();
    const settings = await settingsResponse.json();
    
    return { user, settings };
  } catch (error) {
    console.error('Error:', error);
  }
}

// 结合使用 Promise 和 async/await
async function processData() {
  const data = await fetchUserWithAsync();
  // 做一些处理...
  return data;
}

// 异步函数始终返回 Promise
processData().then(result => console.log(result));

3.2 Object 的新方法

用于操作对象的新静态方法。

const person = {
  name: 'John',
  age: 30,
  job: 'developer'
};

// Object.values() - 返回对象的所有值数组
console.log(Object.values(person)); // ["John", 30, "developer"]

// Object.entries() - 返回键值对数组
console.log(Object.entries(person));
// [["name", "John"], ["age", 30], ["job", "developer"]]

// 使用 Object.entries() 进行迭代
for (const [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}
// "name: John"
// "age: 30"
// "job: developer"

// Object.entries() 结合 Map
const map = new Map(Object.entries(person));
console.log(map.get('name')); // "John"

// Object.getOwnPropertyDescriptors() - 获取对象的所有属性描述符
const descriptors = Object.getOwnPropertyDescriptors(person);
console.log(descriptors.name);
// { value: "John", writable: true, enumerable: true, configurable: true }

// 用于浅拷贝对象,包括 getter 和 setter
const source = {
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
};
const target = {};
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));

3.3 字符串填充方法

// String.prototype.padStart() - 在字符串开头填充
console.log('5'.padStart(2, '0')); // "05"
console.log('JavaScript'.padStart(15, '=')); // "=====JavaScript"

// String.prototype.padEnd() - 在字符串末尾填充
console.log('5'.padEnd(2, '0')); // "50"
console.log('JavaScript'.padEnd(15, '=')); // "JavaScript====="

// 用于对齐输出
const data = [
  { name: 'John', score: 85 },
  { name: 'Jane', score: 92 },
  { name: 'Bob', score: 78 }
];

for (const item of data) {
  console.log(`${item.name.padEnd(10, ' ')} ${item.score}`);
}
// "John       85"
// "Jane       92"
// "Bob        78"

3.4 函数参数列表和调用中的尾随逗号

// 函数定义中的尾随逗号
function createPerson(
  name,
  age,
  job,  // 这个逗号在 ES2017 中是有效的
) {
  return { name, age, job };
}

// 函数调用中的尾随逗号
createPerson(
  'John',
  30,
  'developer',  // 这个逗号在 ES2017 中是有效的
);

// 数组和对象字面量中的尾随逗号 (早已支持)
const array = [
  1,
  2,
  3,  // 有效的尾随逗号
];

const object = {
  a: 1,
  b: 2,
  c: 3,  // 有效的尾随逗号
};

4. ES2018 (ES9) 新特性

4.1 对象的扩展运算符

// 扩展运算符用于对象
const person = { name: 'John', age: 30 };
const personWithJob = { ...person, job: 'developer' };
console.log(personWithJob); // { name: "John", age: 30, job: "developer" }

// 属性覆盖
const updated = { ...person, age: 31 };
console.log(updated); // { name: "John", age: 31 }

// 对象的剩余模式
const { name, ...rest } = person;
console.log(name); // "John"
console.log(rest); // { age: 30 }

// 只针对自身的可枚举属性
const prototype = { inherited: true };
const obj = Object.create(prototype);
obj.own = 'yes';
const clone = { ...obj };
console.log(clone); // { own: "yes" } - 不包含 inherited 属性

4.2 Promise.prototype.finally()

function fetchData() {
  showLoadingIndicator();
  
  return fetch('/api/data')
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    })
    .then(data => {
      updateUI(data);
      return data;
    })
    .catch(error => {
      showError(error);
      throw error;
    })
    .finally(() => {
      // 无论成功或失败都会执行
      hideLoadingIndicator();
    });
}

4.3 异步迭代

// 异步迭代器
const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      async next() {
        if (i < 5) {
          // 等待 100ms
          await new Promise(resolve => setTimeout(resolve, 100));
          return { value: i++, done: false };
        }
        return { done: true };
      }
    };
  }
};

// for-await-of 循环
async function consumeAsyncIterable() {
  for await (const num of asyncIterable) {
    console.log(num);
  }
}

// 异步生成器
async function* fetchCommits(repo) {
  let url = `https://api.github.com/repos/${repo}/commits`;
  
  while (url) {
    const response = await fetch(url);
    const data = await response.json();
    
    for (const commit of data) {
      yield commit;
    }
    
    // 获取下一页 URL
    url = getNextPageUrl(response.headers);
  }
}

// 使用异步生成器
async function showCommits() {
  for await (const commit of fetchCommits('javascript/javascript')) {
    console.log(commit.sha);
  }
}

4.4 正则表达式的新功能

// 命名捕获组 (?<name>...)
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = dateRegex.exec('2022-05-15');
console.log(match.groups); // { year: "2022", month: "05", day: "15" }
console.log(match.groups.year); // "2022"

// 使用解构获取命名组
const { groups: { year, month, day } } = dateRegex.exec('2022-05-15');
console.log(year, month, day); // "2022" "05" "15"

// 在替换字符串中使用命名组
const rewrittenDate = '2022-05-15'.replace(dateRegex, '$<month>/$<day>/$<year>');
console.log(rewrittenDate); // "05/15/2022"

// 后行断言 (?<=...) 和 (?<!...)
// 肯定后行断言
const priceRegex = /(?<=\$)\d+(\.\d+)?/;
console.log(priceRegex.exec('Item costs $10.99')); // ["10.99", ".99"]

// 否定后行断言
const notPriceRegex = /(?<!\$)\d+(?:\.\d+)?/;
console.log(notPriceRegex.exec('Temperature is 23.5 degrees')); // ["23.5", ".5"]

// s 标志 (dotAll 模式)
console.log(/hello.world/.test('hello\nworld')); // false
console.log(/hello.world/s.test('hello\nworld')); // true

// Unicode 属性转义 \p{...} 和 \P{...}
console.log(/\p{Script=Greek}/u.test('π')); // true
console.log(/\p{Letter}/u.test('A')); // true
console.log(/\P{Digit}/u.test('A')); // true

5. ES2019 (ES10) 新特性

5.1 Array.prototype.flat() 和 Array.prototype.flatMap()

// Array.prototype.flat() - 扁平化嵌套数组
const nestedArray = [1, 2, [3, 4, [5, 6]]];
console.log(nestedArray.flat()); // [1, 2, 3, 4, [5, 6]]
console.log(nestedArray.flat(2)); // [1, 2, 3, 4, 5, 6]

// 指定深度
console.log(nestedArray.flat(1)); // [1, 2, 3, 4, [5, 6]]
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4, 5, 6]

// 移除空洞
const arrayWithHoles = [1, 2, , 4, 5];
console.log(arrayWithHoles.flat()); // [1, 2, 4, 5]

// Array.prototype.flatMap() - 先映射后扁平化
const sentences = ['Hello world', 'JavaScript is fun'];
console.log(sentences.map(s => s.split(' '))); // [["Hello", "world"], ["JavaScript", "is", "fun"]]
console.log(sentences.flatMap(s => s.split(' '))); // ["Hello", "world", "JavaScript", "is", "fun"]

// flatMap 等价于 map().flat(1)
console.log(sentences.map(s => s.split(' ')).flat()); // ["Hello", "world", "JavaScript", "is", "fun"]

5.2 Object.fromEntries()

// 将键值对列表转换为对象
const entries = [
  ['name', 'John'],
  ['age', 30],
  ['job', 'developer']
];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: "John", age: 30, job: "developer" }

// 结合 Object.entries() 使用
const person = { name: 'John', age: 30, job: 'developer' };
const uppercaseKeys = Object.fromEntries(
  Object.entries(person).map(([key, value]) => [key.toUpperCase(), value])
);
console.log(uppercaseKeys); // { NAME: "John", AGE: 30, JOB: "developer" }

// 将 Map 转换为对象
const map = new Map([
  ['name', 'John'],
  ['age', 30]
]);
const objFromMap = Object.fromEntries(map);
console.log(objFromMap); // { name: "John", age: 30 }

// 将查询字符串转换为对象
const queryString = 'name=John&age=30&job=developer';
const params = Object.fromEntries(
  queryString.split('&').map(param => param.split('='))
);
console.log(params); // { name: "John", age: "30", job: "developer" }

5.3 String.prototype.trimStart() 和 String.prototype.trimEnd()

// String.prototype.trimStart() - 移除字符串开头的空白
const greeting = '   Hello world!   ';
console.log(greeting.trimStart()); // "Hello world!   "

// String.prototype.trimEnd() - 移除字符串末尾的空白
console.log(greeting.trimEnd()); // "   Hello world!"

// 等同于 trimLeft 和 trimRight
console.log(greeting.trimLeft()); // "Hello world!   "
console.log(greeting.trimRight()); // "   Hello world!"

// 移除所有空白
console.log(greeting.trim()); // "Hello world!"

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到