ES6(ECMAScript 2015)是 JavaScript 的第六个主要版本,它引入了许多新的语言特性和语法。下面是ES6中一些常用的新语法特性:

一、块级作用域和let、const关键字
ES6中引入了块级作用域,即通过使用花括号({ })将一段代码封装在一个作用域中。在块级作用域中,可以使用let和const关键字声明变量,这两个关键字可以避免变量提升和变量的重复声明。
1.1 let和 const 是ES6中引入的新的变量声明方式,相对于ES5中的 var,它们具有以下区别
使用 var 声明的变量是函数作用域或全局作用域,什么意思不用我多说了吧,就是var这个玩意你的的作用域要么在这个函数里面都可以被调用,要么不在函数中的话全局都能被调用就算是{}块级作用域也拦不住你。
而 let 和 const 声明的变量是块级作用域。块级作用域是指变量只在 {} 内有效。这意味着在块级作用域外无法访问块级作用域内的变量,这与使用 var 声明的变量是不同的。
1.2 let与const的赋值
使用 let 声明的变量可以被重新赋值,而使用 const 声明的变量一旦被赋值后就不能再被重新赋值。这是因为 const 声明的变量被视为常量,其值不能被修改。而作为ES5的var当然也是可以被重复赋值的了。
1.3变量提升
使用 var 声明的变量存在变量声明提升的问题,而 let 和 const 声明的变量不存在变量声明提升的问题。
那何为变量提升呢?
答:变量声明提升是JavaScript中的一种特性,它会在代码执行之前将所有的变量声明都提升到作用域的顶部。这意味着可以在变量声明之前使用变量。
具体来说,当使用 var 关键字声明一个变量时,JavaScript 引擎会将这个变量的声明提升到所在作用域的顶部。这样,在变量声明之前使用这个变量时,变量会被视为已经声明但未被赋值,其值为 undefined。

console.log(x) // 输出:undefined
var x = 10

console.log(y) // 会抛出 ReferenceError 异常
let y = 10

console.log(z) // 会抛出 ReferenceError 异常
const z = 10

在上面的代码中,使用 var 声明的变量 x 存在变量声明提升的问题,因此在变量声明前访问 x 不会抛出异常,但其值为 undefined。使用 let 和 const 声明的变量 y 和 z 不存在变量声明提。
需要注意的是,变量声明提升只会提升变量声明本身,而不会提升变量赋值,因此,尽管变量声明提升能够使我们在变量声明之前使用变量,但这种行为可能会导致代码可读性差,建议在变量使用之前就进行声明。

二、箭头函数
箭头函数是一种新的函数定义方式,它可以更简洁地定义函数,减少了代码的冗余。箭头函数可以省略函数关键字 function 和 return 关键字,并且可以使用简化的语法来定义函数的参数和函数体。
1.如果函数体只有一条语句,可以省略花括号和 return 关键字 例子:const sum = (a, b) => a + b;
2.语法简洁:箭头函数的语法非常简洁,可以让代码更加清晰易懂。
3.this 绑定:箭头函数的 this 绑定是词法作用域的,而不是动态绑定,这使得箭头函数在使用 this 变量时更加直观。在箭头函数中,this 指向定义时的父作用域的 this 值,而不是调用时的 this 值。这使得箭头函数特别适用于回调函数和嵌套函数。

class Timer {
  constructor() {
    this.seconds = 0;
  }
  
  start() {
    setInterval(() => {
      this.seconds++;
      console.dir(this);
    }, 1000);
  }
}

在这个示例中,start 方法中的箭头函数中使用了 this 变量,它指向了定义时的 Timer 对象。如果在箭头函数中使用了普通函数的语法,那么 this 将会指向全局对象,这会导致错误的行为。我们在start()调用console.dir(this)发现这个函数的this会指向他的父级class Timer

模板字符串
模板字符串是一种新的字符串定义方式,它可以在字符串中插入表达式和变量,使得字符串拼接更加简单和直观。模板字符串使用反引号(`)包裹,可以使用 ${} 插入表达式或变量。

const name = 'Alice';
const age = 30;
console.log(`My name is ${name} and I'm ${age} years old.`);

上面的代码中,我们使用反引号()来表示一个字符串,其中使用${}符号来包含变量name和表达式age,它们会被自动求值,并将结果插入到字符串中。最终输出的字符串为My name is Alice and I'm 30 years old.`。

四、解构赋值
解构赋值是一种新的变量赋值方式,可以方便地从对象或数组中提取值,并赋值给新的变量。解构赋值可以大大减少代码量,使代码更加简洁明了。
解构赋值分为对象解构赋值和数组解构赋值
4.1对象解构赋值
对象解构赋值可以从对象中提取出属性值并赋值给变量,其语法如下:

const { 属性1, 属性2, ... } = 对象

其中,属性1、属性2等表示对象中需要提取的属性名,可以使用逗号分隔多个属性名。对象表示需要提取属性的对象。
如果对象中的属性名与变量名不一致,可以使用冒号(:)指定新的变量名。示例代码如下:

const user = { name: '张三', age: 18, sex: '男' }

const { name: userName, age: userAge } = user

console.log(userName) // 输出:张三
console.log(userAge) // 输出:18

上述代码中,我们使用对象解构赋值提取了对象 user 中的 name 和 age 属性值,分别赋值给变量 userName 和 userAge。

如果对象中不存在某个属性,使用对象解构赋值会导致变量的值为 undefined。为了避免这种情况,可以使用默认值,示例代码如下:

const user = { name: '张三', age: 18 }

const { name, age, sex = '男' } = user

console.log(name) // 输出:张三
console.log(age) // 输出:18
console.log(sex) // 输出:男

4.2数组解构赋值
数组解构赋值可以从数组中提取出元素值并赋值给变量,其语法如下:
const [ 元素1, 元素2, ... ] = 数组
其中,元素1、元素2等表示需要提取的数组元素值,可以使用逗号分隔多个元素值。数组表示需要提取元素的数组。

五、类和继承
ES6中引入了类和继承的概念,使得面向对象编程更加直观和易于理解。类可以通过 class 关键字定义,可以包含属性和方法,而继承可以通过 extends 关键字实现,使得代码的复用性更高。

六、Promise对象
Promise是一种新的异步编程方式,可以处理异步操作中的回调地狱问题,并使得代码更加简洁和易于理解。Promise可以通过 then() 方法和 catch() 方法处理异步操作的结果和错误。
Promise:一种处理异步操作的方式,它提供了一种更简单的方式来编写异步代码,并且可以避免回调地狱。
6.1异步编程
在讲解Promise对象之前我们先了解一下什么是异步编程吧,异步编程是一种编程模型,它允许在执行长时间操作时,程序不必停止执行等待该操作完成,而是可以继续执行其他任务。异步编程在处理网络请求、文件操作、定时任务等场景下非常常见。

在传统的同步编程中,程序执行时会一步一步按照代码的顺序依次执行,每个操作必须等待前一个操作完成后才能进行下一步操作。在这种情况下,如果程序执行一个耗时较长的操作,整个程序就会被阻塞,直到该操作完成。这种情况下程序的执行效率非常低下,因为大量的时间都被浪费在等待上。

异步编程通过使用回调函数、事件监听、Promise等方式,将长时间的操作变成非阻塞的,程序在等待该操作完成时可以继续执行其他任务,从而提高了程序的执行效率。异步编程的核心思想是将异步操作封装成一个可回调的函数,在异步操作完成后再回调该函数处理结果。

在JavaScript中,异步编程非常常见,因为JavaScript是一种单线程的语言,在浏览器中处理用户交互、网络请求等任务时必须使用异步编程模型。常见的异步编程模型包括回调函数、Promise、async/await等,它们可以帮助我们更好地管理异步任务,提高程序的执行效率。

6.2详细讲解Promise对象
Promise是一种用于异步编程的对象,它可以将异步操作封装成一个可链式调用的函数链,让异步操作变得更加优雅和易于理解。在ES6中,Promise被正式引入标准库,并成为了JavaScript中处理异步操作的标准方法。

Promise的主要特点是可以在异步操作结束之前,暂时挂起操作并返回一个Promise对象,然后可以在后续的回调函数中处理Promise对象的状态。一个Promise对象可以处于以下三种状态中的一种:

  • pending:初始状态,表示Promise对象正在执行中。
  • fulfilled:表示Promise对象已经成功完成了操作。
  • rejected:表示Promise对象执行过程中发生了错误。

一个Promise对象的状态只能由pending转变为fulfilled或rejected,一旦状态转变为fulfilled或rejected,就不可再改变。

使用Promise对象的一般流程如下:

  • 创建一个Promise对象,该对象表示一个异步操作。
  • 在Promise对象中编写异步操作的代码。
  • 在异步操作完成后,调用resolve()函数将Promise对象的状态转变为fulfilled,或调用reject()函数将Promise对象的状态转变为rejected。
  • 在Promise对象上调用then()函数或catch()函数,以处理Promise对象的状态。

Promise对象的特点是可链式调用,也就是说,在一个Promise对象上可以连续调用多个then()函数,以处理异步操作的返回值。此外,Promise对象还可以通过Promise.all()函数将多个Promise对象合并成一个Promise对象,并在所有Promise对象都成功完成后处理合并后的结果。

function asyncOperation() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve('Async operation completed successfully.');
      } else {
        reject('Async operation failed.');
      }
    }, 1000);
  });
}

asyncOperation()
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });



上面的代码中,我们创建了一个Promise对象,并在Promise对象中编写了一个异步操作,使用setTimeout()函数模拟了一个异步操作。在异步操作完成后,我们调用resolve()函数将Promise对象的状态转变为fulfilled,并传递了一个成功的消息。在then()函数中,我们通过回调函数处理了Promise对象的状态。如果异步操作发生错误,我们调用reject()函数将Promise对象的状态转变为rejected,并传递了一个错误消息。在catch()函数中,我们通过回调函数处理了Promise对象的错误状态。

6.3Promise怎么使用多个then()函数
当使用Promise时,我们可以在其实例上调用then()方法来注册回调函数,以处理异步操作的结果。如果我们需要对同一个Promise实例的结果进行多次处理,我们可以在该Promise实例上多次调用then()方法。每个then()方法都会返回一个新的Promise实例,因此我们可以将多个then()方法串联起来形成一个Promise链。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(42);
  }, 1000);
});

promise.then((result) => {
  console.log(`First result: ${result}`);
  return result * 2;
}).then((result) => {
  console.log(`Second result: ${result}`);
  return result * 3;
}).then((result) => {
  console.log(`Third result: ${result}`);
});

在上面的示例中,我们使用了三个then()方法来处理Promise的结果。第一个then()方法接收到Promise的结果并输出结果值,然后返回该结果的两倍。第二个then()方法接收到上一个then()方法的返回结果并输出结果值,然后返回该结果的三倍。第三个then()方法接收到上一个then()方法的返回结果并输出结果值。

6.4怎么使用Promise.all()
Promise的常用方法是Promise.all(),它可以用于将多个Promise实例组合成一个新的Promise实例,用于等待所有Promise实例完成后统一处理结果。Promise.all()方法接收一个Promise实例的数组作为参数,并返回一个新的Promise实例,该新实例的结果是所有Promise实例的结果数组,只有所有Promise实例都完成后,才会触发新Promise实例的回调函数。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello');
  }, 1000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('World');
  }, 2000);
});

Promise.all([promise1, promise2]).then((results) => {
  console.log(results.join(' '));
});

在上面的示例中,我们定义了两个Promise实例,它们会在1秒和2秒后分别完成,并返回"Hello"和"World"。我们使用Promise.all()方法将这两个Promise实例组合成一个新的Promise实例,当这个新的Promise实例的所有Promise实例都完成后,它的回调函数将会输出"Hello World"。注意,结果数组的顺序与传入的Promise数组的顺序相同。如果有任何一个Promise实例被拒绝,那么Promise.all()返回的Promise实例也将被拒绝,并且其它Promise实例的结果将被忽略。

七、模块化
ES6中引入了模块化的概念,可以更好地管理和组织代码。模块化可以通过 export 和 import 关键字实现,使得代码的复用性更高,同时也方便了代码的维护和管理。
7.1为什么要模块化
在早期的 JavaScript 中,通常使用函数和对象来封装代码,但是这种方式的缺点是容易发生命名冲突、难以管理等问题。为了解决这些问题,现代的 JavaScript 语言(如 ES6)提供了模块化机制,可以更加方便地组织和管理代码。

7.2基本使用
可以让我们使用 import 和 export 关键字来导入和导出模块

// 导入模块
import { foo } from './foo.js';

// 导出模块
export function bar() {
  // ...
}

export 用于导出模块,可以导出变量、函数、类等。有两种导出方式:
7.2.1命名导出(named exports)
使用命名导出可以导出多个变量、函数或类

// foo.js
export const PI = 3.1415926;
export function square(x) {
  return x * x;
}
export class Circle {
  constructor(r) {
    this.radius = r;
  }
  area() {
    return PI * this.radius * this.radius;
  }
}

在另一个文件中,可以使用 import 语句导入这些模块:

// main.js
import { PI, square, Circle } from './foo.js';
console.log(PI); // 3.1415926
console.log(square(2)); // 4
const c = new Circle(3);
console.log(c.area()); // 28.27433388

7.2.2默认导出(default export)
使用默认导出可以导出一个默认的变量、函数或类,每个模块只能有一个默认导出。使用如下:

// bar.js
export default function(x) {
  return x * x;
}

在另一个文件中,可以使用 import 语句导入默认模块:

// main.js
import square from './bar.js';
console.log(square(2)); // 4

import导入
image

八、默认参数:函数参数可以设置默认值,如果未传入参数或者传入 undefined,则会使用默认值。

九、展开运算符(...):可以将数组或对象扩展为单独的参数,或者将多个数组合并为一个数组。

十、Generator:一种可以在函数中暂停和恢复执行的特殊函数,可以生成迭代器和异步操作的处理方式。

十一、Map 和 Set:新的集合类型,提供了一种更快速、更方便的方式来处理数据结构。

十二、Proxy 和 Reflect:两种新的对象类型,提供了更多的控制和元编程的能力。