一、ES6简介

1. 什么是ES6

ES6,全称ECMAScript 6,是JavaScript语言的第六个标准版本,于2015年6月正式发布,也被称为ECMAScript 2015。ES6引入了许多新特性,使JavaScript更加强大和灵活,极大地提升了开发效率和代码质量。

2. 与之前版本的主要区别

ES6相比ES5引入了大量新特性,主要包括:

  • 新的变量声明方式(let、const)
  • 箭头函数
  • 模板字符串
  • 解构赋值
  • 扩展运算符
  • 类和模块
  • Promise对象
  • 等等

二、ES6新特性

1. 变量声明

let和const

ES6引入了letconst两个新的变量声明关键字,用于替代var

let声明
  • let声明的变量只在其声明的块级作用域内有效
  • 不存在变量提升
  • 暂时性死区:在声明前使用会报错
  • 不允许重复声明
// 块级作用域示例
{
  let a = 10;
  var b = 1;
}
console.log(b); // 1
console.log(a); // ReferenceError: a is not defined
const声明
  • const声明一个只读的常量,一旦声明,常量的值就不能改变
  • let一样具有块级作用域
  • 必须在声明时初始化
const PI = 3.1415926;
PI = 3.14; // TypeError: Assignment to constant variable

注意:const声明的对象或数组,其内部属性是可以修改的

const obj = { name: 'Alice' };
obj.name = 'Bob'; // 可以修改
obj = {}; // TypeError: Assignment to constant variable

var、let、const比较

特性

var

let

const

作用域

函数作用域

块级作用域

块级作用域

变量提升

重复声明

允许

不允许

不允许

初始化要求

非必须

非必须

必须

可修改性

可修改

可修改

不可修改(对象内部属性可修改)


2. 箭头函数

(1)箭头函数比普通函数更加简洁

  • 如果没有参数,就直接写一个空括号即可
  • 如果只有一个参数,可以省去参数的括号
  • 如果有多个参数,用逗号分割
  • 如果函数体的返回值只有一句,可以省略大括号
  • 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
let fn = () => void doesNotReturn();

(2)箭头函数没有自己的this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

而且由于它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向

var id = 'Global';
let fun1 = () => {
    console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(5)箭头函数不能作为构造函数使用

不能使用new,没有prototype属性

构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。

(6)箭头函数没有自己的arguments

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。

可以使用剩余参数代替:

const sum = (...args) => args.reduce((total, num) => total + num, 0);
console.log(sum(1, 2, 3, 4)); // 10

3. 模板字符串

(1)基本语法

模板字符串使用反引号(`)包裹,可以包含多行文本和嵌入表达式。

// 基本用法
const name = 'Alice';
const greeting = `Hello, ${name}!`;   //1.字符串插值
console.log(greeting); // Hello, Alice!

// 2. 支持直接换行,无需使用\n
const multiLine = `这是第一行    
这是第二行
这是第三行`;
console.log(multiLine);
/*
这是第一行
这是第二行
这是第三行
*/

(2)特点

  1. 字符串插值:使用${expression}在字符串中嵌入表达式
  2. 多行字符串:支持直接换行,无需使用\n
  3. 标签模板:可以与函数结合使用,进行更复杂的字符串处理
// 字符串插值中可以包含任何有效的JavaScript表达式
const a = 5;
const b = 10;
console.log(`a + b = ${a + b}`); // a + b = 15
//3. 可以与函数结合使用
console.log(`今天是${new Date().toLocaleDateString()}`); // 今天是2023/5/20

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

const name = 'Alice';
const age = 25;
const result = highlight`我叫${name},今年${age}岁。`;
console.log(result); // 我叫<strong>Alice</strong>,今年<strong>25</strong>岁。

(3)与传统字符串拼接的比较

// 传统字符串拼接
const name = 'Alice';
const age = 25;
const oldWay = '我叫' + name + ',今年' + age + '岁。';

// 模板字符串
const newWay = `我叫${name},今年${age}岁。`;

模板字符串的优势

  • 更加直观和可读
  • 减少了字符串拼接的错误
  • 支持多行文本
  • 可以嵌入复杂表达式

4. 解构赋值

(1)数组解构

数组解构允许我们从数组中提取值,按照位置对变量赋值。

// 基本用法
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

// 忽略某些值
const [a, , c] = [1, 2, 3];
console.log(a, c); // 1 3

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

// 默认值
const [a = 1, b = 2, c = 3] = [4, 5];
console.log(a, b, c); // 4 5 3

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

(2)对象解构

对象解构允许我们从对象中提取值,按照属性名对变量赋值。

// 基本用法
const { name, age } = { name: 'Alice', age: 25 };
console.log(name, age); // Alice 25

// 给变量取别名
const { name: userName, age: userAge } = { name: 'Alice', age: 25 };
console.log(userName, userAge); // Alice 25

// 默认值
const { name = 'Anonymous', age = 0 } = { name: 'Alice' };
console.log(name, age); // Alice 0

// 嵌套解构
const { address: { city } } = { address: { city: 'Beijing' } };
console.log(city); // Beijing

// 结合剩余运算符
const { name, ...rest } = { name: 'Alice', age: 25, gender: 'female' };
console.log(name, rest); // Alice { age: 25, gender: 'female' }

(3)函数参数解构

解构赋值也可以用于函数参数,使函数调用更加灵活。

// 对象参数解构
function printUser({ name, age = 18 }) {
  console.log(`${name} is ${age} years old`);
}
printUser({ name: 'Alice', age: 25 }); // Alice is 25 years old
printUser({ name: 'Bob' }); // Bob is 18 years old

// 数组参数解构
function sum([a, b]) {
  return a + b;
}
console.log(sum([1, 2])); // 3

(4)解构赋值的应用场景

  1. 交换变量值:无需使用临时变量
  2. 函数返回多个值:可以直接解构接收
  3. 提取JSON数据:快速获取需要的属性
  4. 函数参数默认值:结合解构提供更灵活的默认值
  5. 模块导入:选择性导入模块中的特定功能
// 函数返回多个值
function getUser() {
  return { name: 'Alice', age: 25, gender: 'female' };
}
const { name, age } = getUser();

// 导入模块特定功能
import { useState, useEffect } from 'react';

5. 扩展运算符

(1)数组的扩展运算符

扩展运算符(...)可以将一个数组转为用逗号分隔的参数序列。

// 复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
console.log(arr2); // [1, 2, 3]

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

// 将字符串转为数组
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']

// 结合解构赋值
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first, rest); // 1 [2, 3, 4, 5]

(2)对象的扩展运算符

ES2018引入了对象的扩展运算符,用于取出对象的所有可遍历属性,拷贝到当前对象中。

// 复制对象
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 };
console.log(obj2); // { a: 1, b: 2 }

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

// 属性覆盖
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, b: 3 };
console.log(obj2); // { a: 1, b: 3 }

(3)函数调用中的扩展运算符

扩展运算符可以替代Function.prototype.apply的使用。

// 传统方式
function sum(x, y, z) {
  return x + y + z;
}
const args = [1, 2, 3];
console.log(sum.apply(null, args)); // 6

// 使用扩展运算符
console.log(sum(...args)); // 6

// 构造数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [0, ...arr1, ...arr2, 5];
console.log(arr3); // [0, 1, 2, 3, 4, 5]

(4)剩余参数

剩余参数(Rest parameters)也使用...语法,但作用与扩展运算符相反,用于将多个参数收集为一个数组。

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

function printInfo(name, ...details) {
  console.log(name);
  console.log(details);
}

printInfo('Alice', 25, 'female', 'Beijing');
// Alice
// [25, 'female', 'Beijing']

6. 对象与类

(1)对象字面量增强

ES6对对象字面量进行了增强,使其更加简洁和灵活。

// 属性简写
const name = 'Alice';
const age = 25;
const user = { name, age }; // 等同于 { name: name, age: age }
console.log(user); // { name: 'Alice', age: 25 }

// 方法简写
const obj = {
  sayHi() { // 等同于 sayHi: function() {
    console.log('Hi!');
  }
};
obj.sayHi(); // Hi!

// 计算属性名
const key = 'name';
const user = {
  [key]: 'Alice',
  [`user_${key}`]: 'Bob'
};
console.log(user); // { name: 'Alice', user_name: 'Bob' }

(2)类的定义与使用

ES6引入了类(Class)的概念,作为对象的模板。通过class关键字,可以定义类。

// 类的基本语法
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
  
  get info() {
    return `${this.name}, ${this.age} years old`;
  }
  
  set info(value) {
    [this.name, this.age] = value.split(',');
    this.age = parseInt(this.age);
  }
  
  static isAdult(age) {
    return age >= 18;
  }
}

const alice = new Person('Alice', 25);
alice.sayHello(); // Hello, my name is Alice
console.log(alice.info); // Alice, 25 years old
alice.info = 'Bob,30';
console.log(alice.name); // Bob
console.log(alice.age); // 30
console.log(Person.isAdult(20)); // true

(3)类的继承

ES6的类支持通过extends关键字实现继承。

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类的constructor
    this.breed = breed;
  }
  
  speak() {
    console.log(`${this.name} barks.`);
  }
  
  getBreed() {
    console.log(`${this.name} is a ${this.breed}.`);
  }
}

const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex barks.
dog.getBreed(); // Rex is a German Shepherd.

(4)类与传统构造函数的比较

类实际上是构造函数的语法糖,但提供了更清晰、更面向对象的语法。

// 传统构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

Person.isAdult = function(age) {
  return age >= 18;
};

// ES6类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
  
  static isAdult(age) {
    return age >= 18;
  }
}

7. 模块化

(1)模块的导出与导入

ES6模块化允许JavaScript程序分割成可按需导入的单独模块。

导出(export)
// 命名导出
export const name = 'Alice';
export function sayHello() {
  console.log('Hello!');
}

// 或者集中导出
const name = 'Alice';
function sayHello() {
  console.log('Hello!');
}
export { name, sayHello };

// 导出时重命名
export { name as userName, sayHello as greet };

// 默认导出(每个模块只能有一个默认导出)
export default function() {
  console.log('Default export');
}
导入(import)
// 导入命名导出
import { name, sayHello } from './module.js';

// 导入时重命名
import { name as userName, sayHello as greet } from './module.js';

// 导入所有导出并绑定到一个对象
import * as module from './module.js';
console.log(module.name);
module.sayHello();

// 导入默认导出
import defaultFunction from './module.js';

// 同时导入默认导出和命名导出
import defaultFunction, { name, sayHello } from './module.js';

(2)动态导入

ES2020引入了动态导入功能,使用import()函数按需加载模块。

button.addEventListener('click', async () => {
  const module = await import('./module.js');
  module.default(); // 使用默认导出
  module.sayHello(); // 使用命名导出
});

(3)模块化的优势

  1. 命名空间隔离:避免全局变量污染
  2. 代码重用:模块可以在不同项目中重用
  3. 依赖管理:明确模块之间的依赖关系
  4. 按需加载:可以动态导入模块,提高性能

8. Promise与异步编程

(1)Promise基础

Promise是异步编程的一种解决方案,比传统的回调函数更加优雅。

// 创建Promise
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('操作成功');
    } else {
      reject('操作失败');
    }
  }, 1000);
});

// 使用Promise
promise
  .then(result => {
    console.log(result); // 操作成功
  })
  .catch(error => {
    console.error(error); // 操作失败
  })
  .finally(() => {
    console.log('无论成功还是失败,都会执行');
  });

(2)Promise链式调用

Promise可以链式调用,避免回调地狱。

// 回调地狱
getUser(userId, function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetails(orders[0].id, function(details) {
      // 处理订单详情
    });
  });
});

// Promise链式调用
getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetails(orders[0].id))
  .then(details => {
    // 处理订单详情
  })
  .catch(error => {
    // 统一处理错误
  });

(3)Promise.all和Promise.race

Promise提供了一些静态方法来处理多个Promise。

// Promise.all:所有Promise都成功才成功,有一个失败就失败
const promises = [
  fetch('/api/users'),
  fetch('/api/orders'),
  fetch('/api/products')
];

Promise.all(promises)
  .then(([users, orders, products]) => {
    // 所有请求都成功
  })
  .catch(error => {
    // 至少有一个请求失败
  });

// Promise.race:返回最先完成的Promise的结果
const promise1 = new Promise(resolve => setTimeout(() => resolve('一秒后完成'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('两秒后完成'), 2000));

Promise.race([promise1, promise2])
  .then(result => console.log(result)) // 一秒后完成
  .catch(error => console.error(error));

(4)async/await

ES2017引入了async/await语法,使异步代码看起来像同步代码,更加直观。

// 使用async/await
async function fetchUserData(userId) {
  try {
    const user = await getUser(userId);
    const orders = await getOrders(user.id);
    const details = await getOrderDetails(orders[0].id);
    return details;
  } catch (error) {
    console.error('获取用户数据失败:', error);
  }
}

// 调用异步函数
fetchUserData(123).then(details => {
  console.log(details);
});

// 并行执行多个异步操作
async function fetchAllData() {
  try {
    const [users, orders, products] = await Promise.all([
      fetch('/api/users').then(res => res.json()),
      fetch('/api/orders').then(res => res.json()),
      fetch('/api/products').then(res => res.json())
    ]);
    return { users, orders, products };
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐