likes
comments
collection
share

2.8万字总结!!ES6到ES12常用新特性!

作者站长头像
站长
· 阅读数 9

ES是什么?

ES是ECMAScript的缩写,也就是JavaScript的标准化规范。ECMAScript是一种由Ecma国际组织制定的脚本语言标准,它定义了JavaScript的语法、类型、操作符、对象和函数等基本组件。ES6(也称为ES2015)是ECMAScript的第六个版本,引入了许多新的语言特性和改进,如箭头函数、模板字面量、解构赋值等。随后的版本(如ES7、ES8等)也引入了许多新的功能和语法糖,以进一步改进和扩展JavaScript。

时间轴

...2009ES5 (ECMAScript5)2015ES6 (ECMAScript2015)2016ES7 (ECMAScript2016)2017ES8 (ECMAScript2017)2018ES9 (ECMAScript2018)2019ES10(ECMAScript2019)2020ES11(ECMAScript2020)2021ES12(ECMAScript2021)...ECMAScript 版本时间轴

各版本特性总结

版本特性描述
ES6let 和 const 关键字引入了块级作用域声明变量的关键字 let 和常量声明变量的关键字 const
ES6函数参数默认值允许在函数定义时为参数设置默认值
ES6箭头函数使用箭头 (=>) 定义函数,简化了函数的写法,并且自动绑定了当前作用域的 this
ES6模板字符串使用反引号 (`) 来定义字符串,可以在字符串中插入变量或表达式,并支持多行字符串
ES6扩展操作符使用扩展操作符 (...) 可以将数组或对象展开为单独的元素
ES6解构赋值可以从数组或对象中提取值并赋给变量
ES6对象字面量简化简化了对象的定义和属性的赋值方式
ES6引入了类和继承的概念,使得 JavaScript 更像面向对象编程语言
ES6模块化支持使用 import 和 export 关键字进行模块化开发,可以方便地导入和导出模块
ES6Promise提供了一种更优雅的方式来处理异步操作,解决了回调地狱的问题
ES6Symbol引入了一种新的原始数据类型 Symbol,可以用来创建唯一的标识符
ES6Map/WeakMap 和 Set/WeakSet 数据结构提供了更灵活和高效的数据结构,Map 是一种键值对的集合,Set 是一种无重复值的集合,WeakMap 和 WeakSet 是弱引用版本,可以更好地处理内存和垃圾回收的问题
ES6迭代器(Iterator)和 for...of迭代器提供了一种遍历集合的方式,for...of 循环可以直接遍历可迭代对象
ES6生成器(Generator)可以生成多个值的函数,使用 function* 和 yield 关键字定义,可以暂停和恢复函数的执行
ES6Proxy提供了拦截和自定义操作的机制,可以代理对目标对象的访问和修改操作
ES6Reflect提供了一组静态方法来操作对象,比如获取对象的属性描述符、动态调用对象的方法等
ES6数组对象扩展引入了一些新的方法和属性,比如 Array.from()、Array.of()、Array.prototype.includes() 等,方便了数组的创建和操作
ES6字符串对象扩展引入了一些新的方法和属性,比如 String.prototype.startsWith()、String.prototype.endsWith()、String.prototype.includes() 等,方便了字符串的处理
ES6Math 对象扩展引入了一些新的方法和常量,比如 Math.trunc()、Math.sign()、Math.PI 等,提供了更多的数学计算功能
ES6Object 对象扩展引入了一些新的方法,比如 Object.assign()、Object.keys()、Object.values() 等,方便了对象的操作
ES6正则对象扩展引入了一些新的方法,比如 RegExp.prototype.flags、RegExp.prototype.sticky 等,增强了正则表达式的功能
ES7Array.prototype.includes()方法判断数组中是否包含指定的元素,返回布尔值
ES7指数操作符 **计算指数幂的运算符
ES8async/await提供了更简洁和可读性更好的异步编程方式
ES8Object.entries()返回对象自身可枚举属性的键值对数组
ES8Object.values()返回对象自身可枚举属性的值组成的数组
ES8Object.getOwnPropertyDescriptors()返回指定对象所有自身属性的描述符
ES8padStart()和padEnd()在字符串的开头或结尾填充指定的字符,使字符串达到指定的长度
ES8ShareArrayBuffer一种新的共享内存对象,用于在多个线程之间共享数据,但因安全问题暂时在 Chrome、FireFox、Safari 中被禁用
ES9for await...of遍历异步可迭代对象的每个元素
ES9Rest/Spread 属性允许使用...语法来获取剩余的参数或将数组或对象展开为函数的参数
ES9Promise.finally()无论 Promise 对象的状态如何,都会执行的回调函数
ES9正则表达式扩展引入了一些新的功能,包括反向断言、命名捕获组、s 修饰符(dotAll 模式)、Unicode 属性转义等
ES10数组扁平化方法使用 flat() 方法将多维数组转换为一维数组
ES10字符串去除开头和结尾的空格方法使用 trimStart() 和 trimEnd() 方法去除字符串开头和结尾的空格
ES10Object.fromEntries将键值对数组转换为对象
ES10Symbol.prototype.description获取 Symbol 对象的描述
ES10Function.prototype.toString()返回函数的源代码字符串
ES10catch 绑定允许不使用参数绑定 catch 语句块中的错误对象
ES10JSON.stringify() 增强支持序列化 BigInt 类型的数据
ES11globalThis提供了一个标准的方式来获取全局对象,不再依赖于具体的环境
ES11BigInt引入了一种新的原始数据类型 BigInt,可以表示任意精度的整数
ES11可选链操作符允许在访问对象的属性时,如果属性不存在,不会报错,而是返回 undefined
ES11空值合并操作符允许在变量为 null 或 undefined 时,使用默认值
ES11String.prototype.matchAll()返回一个迭代器,包含了字符串中与正则表达式匹配的所有结果
ES11import()动态导入模块的方法
ES11Promise.allSettled()返回一个 Promise,等待所有 Promise 完成,并返回一个包含所有 Promise 结果的数组,不会抛出错误
ES12数值分隔符使用下划线 (_) 来分隔数值,提高数值的可读性
ES12逻辑赋值运算符引入了逻辑赋值运算符,简化了变量赋值的操作
ES12String.prototype.replaceAll()替换字符串中的所有匹配项
ES12Promise.any()返回一个 Promise,只要有一个 Promise 成功,就会返回该 Promise 的结果,不会等待其他 Promise 的完成

以上只列举了每个版本的一些主要特性,还有其他一些较小的更新和改进没有在表格中列出。

ES6

ES6是ECMAScript 2015的简称,是ECMAScript的第六个版本。它在2015年发布,也被称为ES2015。ES6引入了许多新的语法和功能,大大改进了JavaScript的编程体验和开发效率。一些常见的ES6特性包括箭头函数、类和模块的支持、模板字符串、解构赋值、默认参数值、扩展运算符、Promise等。ES6的新增特性使得JavaScript在语法和功能上更加现代化和强大,成为前端开发中使用最广泛的ECMAScript版本之一。

let和const关键字

  • let关键字用于声明一个块级作用域的变量。与以前的var关键字不同,let声明的变量只在其所在的块级作用域内有效,而不会被提升到函数作用域。这意味着在使用let声明的变量之前,必须先进行声明,否则会抛出ReferenceError错误。

  • const关键字用于声明一个常量,其值在声明后不能再改变。const声明的变量也是块级作用域的,与let类似,但其值是不可变的。

if (true) {
  let x = 10; // 块级作用域变量
  const y = 20; // 块级作用域常量
  console.log(x); // 输出: 10
  console.log(y); // 输出: 20
  y = 10;//报错: Assignment to constant variable.
}

console.log(x); // 报错: x is not defined
console.log(y); // 报错: y is not defined

函数参数默认值

  • 在ES6中,我们可以在函数参数中设置默认值。这意味着如果调用函数时没有为参数提供值,它们将使用默认值。这对于简化函数调用和处理缺少参数的情况非常有用。
function greet(name = "World") {
  console.log(`Hello, ${name}!`);
}

greet(); // 输出:Hello, World!
greet("hhh"); // 输出:Hello, hhh!

箭头函数

  • 箭头函数具有简洁的语法、清晰的上下文、继承外部作用域的arguments等特点,适合简化代码、避免this指向问题和明确的参数处理,但不适合用于构造函数和需要自己的this值的场景。
// 箭头函数示例
const add = (a, b) => a + b;
console.log(add(2, 3)); // 输出: 5

// 函数表达式示例
const multiply = function(a, b) {
  return a * b;
};
console.log(multiply(2, 3)); // 输出: 6

模板字符串

  • 模板字符串是ES6中引入的一种新的字符串语法。它允许在字符串中插入变量或表达式,而不需要使用字符串拼接符号。模板字符串使用反引号``包围,并使用${}语法来插入变量或表达式。

  • ${}语法中,我们可以放置任何有效的JavaScript表达式,这些表达式的值将被插入到字符串中。

const name = "world";
console.log(`hello ${name}`); // 输出: hello world

//插入dom标签
const parent = document.querySelector('.parent');
const content = '这是一个div标签'
const templateString = `<div>${content}</div>`;
parent.innerHTML = templateString;

扩展操作符

  • 扩展操作符用于展开可迭代对象(如数组、字符串等),将其元素逐个展开,以便于在函数调用、数组字面量、对象字面量等地方使用。

  • 在使用扩展操作符时,你需要在要展开的可迭代对象前面加上三个点(...)。

  1. 展开数组:
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
  1. 传递参数给函数:
function sum(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];

const result = sum(...numbers); // 6
  1. 浅拷贝数组或对象:
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // [1, 2, 3]

const obj1 = {name: 'Alice', age: 20};
const obj2 = {...obj1}; // {name: 'Alice', age: 20}

解构赋值

  • ES6的解构赋值语法允许我们从数组或对象中提取值,并将它们赋给变量。这使得我们可以更简洁地进行变量赋值操作。

  • 解构赋值可以用于数组和对象。下面是一些示例:

  1. 数组解构赋值:
let numbers = [1, 2, 3];
let [a, b, c] = numbers;
console.log(a); // 输出 1
console.log(b); // 输出 2
console.log(c); // 输出 3
  1. 对象解构赋值:
let person = { name: 'John', age: 20 };
let { name, age } = person;
console.log(name); // 输出 'John'
console.log(age); // 输出 20

除了基本的数组和对象解构赋值外,ES6的解构赋值还提供了一些其他的操作。

  1. 剩余项(Rest)操作符:可以使用...语法来捕获剩余的项,并将它们赋给一个数组。这在处理变长参数或动态长度的数组时非常有用。例如:
let numbers = [1, 2, 3, 4, 5];
let [a, b, ...rest] = numbers;
console.log(a); // 输出 1
console.log(b); // 输出 2
console.log(rest); // 输出 [3, 4, 5]
  1. 解构赋值还支持默认值,当解构的值为undefined时,会使用默认值:
let person = { name: 'John' };
let { name, age = 18 } = person;
console.log(name); // 输出 'John'
console.log(age); // 输出 18
  1. 嵌套解构:可以在解构赋值中嵌套使用数组和对象的解构。例如:
let numbers = [1, [2, 3], 4];
let [a, [b, c], d] = numbers;
console.log(a); // 输出 1
console.log(b); // 输出 2
console.log(c); // 输出 3
console.log(d); // 输出 4
  1. 对象属性别名:可以为解构赋值的变量设置别名,使用冒号来指定别名。例如:
let person = { name: 'John', age: 20 };
let { name: fullName, age } = person;
console.log(fullName); // 输出 'John'
console.log(age); // 输出 20
  1. 解构赋值还可以在函数参数中使用,方便地提取函数参数中的值:
function greet({ name, age }) {
  console.log(`Hello, ${name}! You are ${age} years old.`);
}

let person = { name: 'John', age: 20 };
greet(person); // 输出 'Hello, John! You are 20 years old.'

对象字面量简化

  • ES6引入了一种更简洁的方式来定义对象字面量,即ES6对象字面量语法。它提供了一种更方便的方法来定义和初始化对象属性。它是ES6中一个非常方便的特性,可以提高代码的可读性和可维护性。

  • 在ES6之前,我们通常使用以下方式定义对象字面量:

var name = 'hhh';
var age = 18;

var person = {
  name: name,
  age: age
};
  • 在ES6中,我们可以使用更简洁的语法来定义对象字面量,还可以直接在对象字面量中定义方法,而不需要使用function关键字:
const name = "hhh";
const age = 18;

const person = {
  name,
  age,
  fun() {
    console.log(this.name,this.age);
  },
};

  • 在ES5中,我们没有类的概念,而是通过构造函数和原型链来实现对象:
function Book(title, author) {
  this.title = title;
  this.author = author;
}

Book.prototype.getSummary = function() {
  return "书名:" + this.title + ",作者:" + this.author;
};

// 创建Book类的实例
var book1 = new Book("三体", "刘慈欣");
console.log(book1.getSummary()); // 书名:三体,作者:刘慈欣
  • ES6引入了类(class)的概念,使得面向对象的编程变得更加直观和易于理解。类是一种蓝图或模板,用于创建具有相同属性和方法的对象。
class Book {
  constructor(title, author) {
    this.title = title;
    this.author = author;
  }

  getSummary() {
    return `书名:${this.title} ,作者:${this.author} `;
  }
}

// 创建Book类的实例
const book1 = new Book("三体", "刘慈欣");
console.log(book1.getSummary()); //书名:三体 ,作者:刘慈欣 

模块化

  • 模块化是一种组织和管理JavaScript代码的方法,它将代码拆分为独立的模块,每个模块都有自己的作用域和功能。这种方法有助于提高代码的可维护性、可重用性和可扩展性。

  • 在ES6之前,JavaScript并没有原生的模块化支持。开发人员通常使用一些第三方库或模式来实现模块化,例如CommonJSAMD

  • 然而,ES6引入了原生的模块化系统,通过importexport关键字来实现。下面是一个示例:

// utils.js
// 导出一个常量
export const PI = 3.14;
// 导出一个函数
export const area = (r) => PI * r ** 2

// main.js
// 导入常量和函数
import { PI, area } from './utils.js';
console.log(PI,area(3));//3.14 28.26

Promise

  • Promise是一个表示异步操作最终完成或失败的对象。

  • 它可以有三种状态:

    • pending(进行中)
    • resolved(已完成)
    • rejected(已失败)
  • 创建一个Promise对象:new Promise((resolve, reject) => {})

    • resolve:将状态从进行中变为完成,在异步操作成功时调用,并将异步操作的结果作为参数传递出去
    • reject:将状态从进行中变为失败,在异步操作失败时调用,并将异步操作的错误作为参数传递出去
  • 主要方法:

    • then():用于处理异步操作成功的情况

    • catch():用于处理异步操作失败的情况。

    • Promise.all():接收一个每个元素都是一个Promise 对象的可迭代对象(如数组)作为参数,它会返回一个新的 Promise 对象,该 Promise 对象的状态取决于传入的所有 Promise 对象的状态(其中任何一个Promise对象状态为 rejected 状态,返回的 Promise 对象状态会立即变为 rejected),返回的 Promise 对象的结果是有序的,与传入的 Promise 对象的顺序相同。

    • Promise.race():和Promise.all()方法类似,但只要有一个 Promise 对象的状态变为 resolved 或 rejected,它就会返回该 Promise 对象的结果。

    • Promise.resolve() :将对象转为状态为resolved的Promise对象(等价于new Promise(resolve => resolve()))

    • Promise.reject() :将对象转为状态为rejected的Promise对象(等价于new Promise((resolve, reject) => reject()))

const myPromise = new Promise((resolve, reject) => {
  // 通过setTimeout模拟了一个耗时1秒的异步操作
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      // 操作成功,调用resolve函数
      resolve(randomNumber);
    } else {
      // 操作失败,调用reject函数
      reject(new Error('操作失败'));
    }
  }, 1000);
});

// 使用then方法处理Promise的结果
myPromise.then((result) => {
  console.log('操作成功:', result);
}).catch((error) => {
  console.log('操作失败:', error);
});
  • Promise.all()Promise.race()
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 1");
  }, 2000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 2");
  }, 1000);
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 3");
  }, 3000);
});

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

Promise.race([promise1, promise2, promise3])
  .then((results) => {
    console.log(results); // Promise 2
  })
  .catch((error) => {
    console.error(error);
  });
  • Promise.resolve()Promise.reject()
// 将一个值转化为 resolved 的 Promise 对象
const promise1 = Promise.resolve(42);
// 将一个值转化为 rejected 的 Promise 对象
const promise2 = Promise.reject(42);

promise1.then((result) => {
  console.log(result); // 输出:42
});

promise2.catch((error) => {
  console.log(error); // 输出:42
});

Symbol

  • 在ES6之前对象属性名都是字符串,这容易造成属性名的冲突。ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它属于 JavaScript 语言的原生数据类型之一。

  • Symbol是一种独一无二且不可修改的数据类型,可以用作对象属性的唯一标识符。它被设计用于创建对象属性的私有成员或者用作常量。

使用Symbol()函数可以创建一个新的Symbol。每次调用Symbol()函数都会返回一个全新且不相等的Symbol。

const mySymbol1 = Symbol();
const mySymbol2 = Symbol('hhh');
console.log(typeof mySymbol1); // symbol
console.log(mySymbol2.toString()); // Symbol(hhh)
console.log(mySymbol2 == Symbol('hhh')); // false

Symbol可以作为对象的属性名来定义对象的私有成员。

const obj = {};

const privateMember = Symbol();
obj[privateMember] = "私有成员";

console.log(obj[privateMember]); // "私有成员"

可以通过Object.getOwnPropertySymbols()方法获取对象的所有Symbol属性。

const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol()]
console.log(obj[symbols[0]]); // "私有成员"

Map/WeakMap和Set/WeakSet数据结构

Map

  • Map是一种键值对的集合(Hash 结构),它类似于对象,但有一些不同之处。Map的键可以是任意类型的值,包括对象和函数,而对象只能使用字符串作为键。此外,Map的键值对是有序的,插入顺序决定了键值对的顺序。

  • 方法

    • get() :返回键值对
    • set() :添加键值对,返回实例
    • delete() :删除键值对,返回布尔值
    • has() :检查键值对,返回布尔值
    • clear() :清除所有成员
    • keys() :返回以键为遍历器的对象
    • values() :返回以值为遍历器的对象
    • entries() :返回以键和值为遍历器的对象
    • forEach() :使用回调函数遍历每个成员
// 创建一个空的Map
let map = new Map();

// 添加键值对
map.set('name', 'John');
map.set('age', 30);

// 获取值
console.log(map.get('name')); // John

// 检查是否包含某个键
console.log(map.has('age')); // true

// 删除键值对
map.delete('age');

//返回长度
console.log(map.size); // 1

WeakMap

  • WeakMap也是一种键值对的集合,但是只接受对象作为键,不接受其他类型的数据。WeakMap中的键是弱引用的,这意味着如果键对象没有其他引用,它会被垃圾回收机制回收,并且对应的键值对也会从WeakMap中被自动移除。

  • 应用

    • 储存DOM节点:DOM节点被移除时自动释放此成员键,不用担心这些节点从文档移除时会引发内存泄漏
    • 部署私有属性:内部属性是实例的弱引用,删除实例时它们也随之消失,不会造成内存泄漏
  • 方法

    • get() :返回键值对
    • set() :添加键值对,返回实例
    • delete() :删除键值对,返回布尔值
    • has() :检查键值对,返回布尔值

Set

  • Set是一种不重复值的集合,类似于数组,但是它的值是唯一的,不会重复。Set可以存储任意类型的值,包括原始类型和对象。

  • 方法

    • add() :添加值,返回实例
    • delete() :删除值,返回布尔值
    • has() :检查值,返回布尔值
    • clear() :清除所有成员
    • keys() :返回以属性值为遍历器的对象
    • values() :返回以属性值为遍历器的对象
    • entries() :返回以属性值和属性值为遍历器的对象
    • forEach() :使用回调函数遍历每个成员
// 创建一个空的Set
let set = new Set();

// 添加值
set.add(1);
set.add(2);
set.add(2);
set.add(3);

// 检查是否包含某个值
console.log(set.has(2)); // true

// 删除值
set.delete(3);

//返回实例成员总数
console.log(set.size); // 2

WeakSet

  • WeakSet是一种弱引用集合,它只能存储对象类型的值,并且这些对象是弱引用的。这意味着如果一个对象在WeakSet中没有任何其他引用,那么这个对象将会被垃圾回收。由于WeakSet的成员是弱引用,因此无法迭代,也无法获取其中的大小或者清空它。

  • 应用

    • 储存DOM节点:DOM节点被移除时自动释放此成员键,不用担心这些节点从文档移除时会引发内存泄漏
    • 临时存放一组对象或存放跟对象绑定的信息:只要这些对象在外部消失,它在WeakSet结构中的引用就会自动被垃圾回收
  • 方法

    • add() :添加值,返回实例
    • delete() :删除值,返回布尔值
    • has() :检查值,返回布尔值

迭代器(Iterator)和for...of

迭代器(Iterator)

  • 迭代器(Iterator)是一种迭代的机制,为各种不同的数据结构提供统一的访问机制。任何数据结构只要内部有 Iterator 接口,就可以完成依次迭代操作。
  • 默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”。
  • 原生具备 Iterator 接口的数据结构如下:
    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的 arguments 对象
    • NodeList 对象
  • 迭代器对象方法:
    • next() :下一步操作,返回{ value,done }(必须部署)
    • return()for-of提前退出调用,返回{ done: true }
    • throw() :不使用,配合Generator函数使用
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

for...of 循环

  • for...of循环用于遍历可迭代对象(例如数组、字符串、Set、Map等),它会迭代对象中的每个元素并执行指定的代码块。

使用for...of循环遍历数组:

let arr = [1, 2, 3, 4];

for(let element of arr) {
  console.log(element);
}
// 输出:
// 1
// 2
// 3
// 4

使用for...of循环遍历字符串:

let str = "Hello";

for(let char of str) {
  console.log(char);
}
// 输出:
// H
// e
// l
// l
// o

for...of循环遍历Set和Map的元素:

let set = new Set([1, 2, 3]);

for(let value of set) {
  console.log(value);
}
// 输出:
// 1
// 2
// 3

let map = new Map([
  ['name', 'John'],
  ['age', 30]
]);

for(let [key, value] of map) {
  console.log(key, value);
}
// 输出:
// name John
// age 30

生成器(Generator)

  • Generator 函数在语法上,可以把它理解成一个状态机,内部封装了多个内部状态。

  • 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

  • 形式上,Generator 函数是一个普通函数,但是有两个特征:

    • function关键字与函数名之间有一个星号;
    • 函数体内部使用yield表达式,定义不同的内部状态
function* helloWorldGenerator() {
  yield "hello";
  yield "world";
  return "ending";
}
var hw = helloWorldGenerator();
console.log(hw.next());// { value: 'hello', done: false }
console.log(hw.next());// { value: 'world', done: false }
console.log(hw.next());// { value: 'ending', done: true }
console.log(hw.next());// { value: undefined, done: true }

Proxy

  • Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

  • Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

  • 使用:const proxy = new Proxy(target, handler)其中target是拦截的目标对象,handler是定制拦截行为

  • 常见的拦截方式

    • get(target, propKey, receiver) :用于拦截某个属性的读取操作
    • set(target, propKey, value, receiver) :用来拦截某个属性的赋值操作,返回布尔值
    • has(target, propKey) :拦截对象属性检查k in obj,返回布尔值
    • deleteProperty(target, propKey) :拦截对象属性删除delete obj[k],返回布尔值
    • defineProperty(target, propKey, propDesc) :拦截对象属性定义Object.defineProperty()Object.defineProperties(),返回布尔值
    • ownKeys(target) :拦截对象属性遍历for-inObject.keys()Object.getOwnPropertyNames()Object.getOwnPropertySymbols(),返回数组
    • apply(target, object, args) :拦截Proxy实例作为函数调用proxy()proxy.apply()proxy.call()

Reflect

  • 在ES5及之前的版本中,对于对象的操作通常是通过Object的方法来完成,比如Object.defineProperty()Object.create()等。这些方法在使用上存在一些不一致和不直观的地方,比如使用Object.defineProperty()来定义属性时,如果属性已经存在,会抛出错误,而Reflect.defineProperty()则会返回false表示定义失败。

  • es6引入的Reflect是一个内置的对象,它提供了一组静态方法,用于操作对象。Reflect的方法和一些Object的方法具有相似的功能,但是使用Reflect方法可以更加简洁和直观;使用Reflect方法进行操作时,返回值可以告诉我们操作是否成功,而不是通过抛出错误来表示操作失败。

  • Reflect对象常用方法:

    • construct(target, argumentsList):用于创建一个类的实例对象。
    • get(target, propKey, receiver):获取对象的属性值。
    • set(target, propKey, value, receiver):设置对象的属性值。
    • has(target, propKey):判断对象是否具有某个属性。
    • deleteProperty(target, propKey):删除对象的属性。
    • apply(function, thisArg, args):调用一个函数,并传入指定的参数。
    • defineProperty(target, propKey, attributes):定义对象的属性。
  • 利用ReflectProxy写一个观察者模式

class Observable {
  constructor() {
    this.data = {}; // 存储数据
    this.observers = new Set(); // 存储观察者
  }

  addObserver(observer) {
    this.observers.add(observer); // 添加观察者到observers集合中
  }

  removeObserver(observer) {
    this.observers.delete(observer); // 从observers集合中删除观察者
  }

  notifyObservers() {
    for (let observer of this.observers) {
      observer.update(this.data); // 通知观察者更新数据
    }
  }

  setData(key, value) {
    Reflect.set(this.data, key, value); // 使用Reflect设置数据
    this.notifyObservers(); // 通知观察者更新数据
  }
}

class Observer {
  constructor(name) {
    this.name = name; // 观察者名称
  }

  update(data) {
    console.log(`${this.name} received data:`, data); // 观察者接收到数据并输出
  }
}

// 创建可观察对象
const subject = new Observable();

// 创建观察者对象
const observer1 = new Observer("Observer 1");
const observer2 = new Observer("Observer 2");

// 将观察者对象添加到主题对象中
subject.addObserver(observer1);
subject.addObserver(observer2);

// 设置数据并通知观察者
subject.setData("name", "Jack");
subject.setData("age", 25);

// 输出结果:
// Observer 1 received data: { name: 'Jack' }
// Observer 2 received data: { name: 'Jack' }
// Observer 1 received data: { name: 'Jack', age: 25 }
// Observer 2 received data: { name: 'Jack', age: 25 }

数组对象扩展

  1. Array.prototype.from(): 将类数组对象或可迭代对象转换为数组。
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const array = Array.from(arrayLike);
console.log(array); // ['a', 'b', 'c']
  1. Array.prototype.of(): 根据传入的参数创建一个新数组。
const array = Array.of(1, 2, 3);
console.log(array); // [1, 2, 3]
  1. Array.prototype.find(): 返回数组中满足条件的第一个元素。
const array = [1, 2, 3, 4, 5];
const found = array.find(element => element > 3);
console.log(found); // 4
  1. Array.prototype.findIndex(): 返回数组中满足条件的第一个元素的索引。
const array = [1, 2, 3, 4, 5];
const index = array.findIndex(element => element > 3);
console.log(index); // 3
  1. Array.prototype.fill(): 用指定的值填充数组。
const array = [1, 2, 3, 4, 5];
array.fill(0, 1, 3);
console.log(array); // [1, 0, 0, 4, 5]
  1. Array.prototype.copyWithin():用于将数组中的一部分元素复制到指定位置,覆盖原有的元素。
const arr = [1, 2, 3, 4, 5];
arr.copyWithin(0, 3); // 将索引为3及其之后的元素复制到索引为0的位置
console.log(arr); // 输出:[4, 5, 3, 4, 5]
  1. Array.prototype.keys():返回一个包含数组中每个索引的新Array Iterator对象。
const arr = ['a', 'b', 'c'];
const iterator = arr.keys();
for (const key of iterator) {
  console.log(key); // 输出:0, 1, 2
}
  1. Array.prototype.values():该方法返回一个包含数组中每个元素的新Array Iterator对象。
const arr = ['a', 'b', 'c'];
const iterator = arr.values();
for (const value of iterator) {
  console.log(value); // 输出:'a', 'b', 'c'
}
  1. Array.prototype.entries():返回一个包含数组中每个索引和对应元素的新Array Iterator对象。
const arr = ['a', 'b', 'c'];
const iterator = arr.entries();
for (const [index, value] of iterator) {
  console.log(index, value); // 输出:0 'a', 1 'b', 2 'c'
}
  1. 数组空位:ES6中对待数组空位的方式有所改变。空位指的是数组中某个位置没有任何值,例如[1, , 3]中的第二个元素是个空位。在ES6之前,对待数组空位的方式是跳过它们,不进行任何操作。但在ES6中,空位被视为undefined的值。例如,使用ES6的数组方法时,空位会被当作undefined处理。

字符串对象扩展

  1. String.prototype.includes():判断字符串是否包含指定的字符,返回布尔值。
const str = "Hello, world!";
console.log(str.includes("world")); // 输出:true
console.log(str.includes("foo")); // 输出:false
  1. String.prototype.startsWith():判断字符串是否以指定的字符开始,返回布尔值。
const str = "Hello, world!";
console.log(str.startsWith("Hello")); // 输出:true
console.log(str.startsWith("foo")); // 输出:false
  1. String.prototype.endsWith():判断字符串是否以指定的字符结束,返回布尔值。
const str = "Hello, world!";
console.log(str.endsWith("world!")); // 输出:true
console.log(str.endsWith("foo")); // 输出:false
  1. String.prototype.repeat():将字符串重复指定次数,返回新的字符串。
const str = "Hello, world!";
console.log(str.repeat(3)); // 输出:Hello, world!Hello, world!Hello, world!
  1. String.prototype.fromCodePoint():根据给定的码点创建一个字符串。它可以将一个或多个码点转换为对应的字符。
console.log(String.fromCodePoint(65)); // 输出:A
console.log(String.fromCodePoint(97, 98, 99)); // 输出:abc
  1. String.prototype.raw():用于获取一个模板字符串的原始字符串形式,忽略其中的转义字符。
const path = 'C:\\Users\\hhh\\Documents\\file.txt';
console.log(String.raw`The file is located at ${path}`); // 输出:The file is located at C:\Users\hhh\Documents\file.txt
  1. String.prototype.codePointAt():用于返回指定位置的字符的码点。
const str = 'abc';
console.log(str.codePointAt(0)); // 输出:97
console.log(str.codePointAt(1)); // 输出:98
console.log(str.codePointAt(2)); // 输出:99
  1. String.prototype.normalize():用于将字符串的 Unicode 标准化形式。它主要用于处理 Unicode 字符串的不同表示方式。
const str = 'c\u0327';
console.log(str.normalize()); // 输出:ç

Math对象扩展

  • 二进制表示法 : 0b或0B开头表示二进制(0bXX0BXX)

  • 二进制表示法 : 0b或0B开头表示二进制(0bXX0BXX)

  • 八进制表示法 : 0o或0O开头表示二进制(0oXX0OXX)

  • Number.EPSILON : 数值最小精度

  • Number.MIN_SAFE_INTEGER : 最小安全数值(-2^53)

  • Number.MAX_SAFE_INTEGER : 最大安全数值(2^53)

  • Number.parseInt() : 返回转换值的整数部分

  • Number.parseFloat() : 返回转换值的浮点数部分

  • Number.isFinite() : 是否为有限数值

  • Number.isNaN() : 是否为NaN

  • Number.isInteger() : 是否为整数

  • Number.isSafeInteger() : 是否在数值安全范围内

  • Math.trunc() : 返回数值整数部分

  • Math.sign() : 返回数值类型(正数1负数-1零0)

  • Math.cbrt() : 返回数值立方根

  • Math.clz32() : 返回数值的32位无符号整数形式

  • Math.imul() : 返回两个数值相乘

  • Math.fround() : 返回数值的32位单精度浮点数形式

  • Math.hypot() : 返回所有数值平方和的平方根

  • Math.expm1() : 返回e^n - 1

  • Math.log1p() : 返回1 + n的自然对数(Math.log(1 + n))

  • Math.log10() : 返回以10为底的n的对数

  • Math.log2() : 返回以2为底的n的对数

  • Math.sinh() : 返回n的双曲正弦

  • Math.cosh() : 返回n的双曲余弦

  • Math.tanh() : 返回n的双曲正切

  • Math.asinh() : 返回n的反双曲正弦

  • Math.acosh() : 返回n的反双曲余弦

  • Math.atanh() : 返回n的反双曲正切

Object对象扩展

  1. Object.is(value1, value2): 用于比较两个值是否相同。与"==="操作符的行为类似,但有两个区别:一是Object.is(NaN, NaN)返回true,而"==="操作符返回false;二是Object.is(+0, -0)返回false,而"==="操作符返回true。
console.log(Object.is(1, 1)); // true
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
  1. Object.assign(target, ...sources): 将一个或多个源对象的属性复制到目标对象中。它返回目标对象。如果多个源对象具有相同的属性,则后面的对象的属性将覆盖前面的对象的属性。
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 3, c: 4 }
  1. Object.getPrototypeOf(obj): 用于获取对象的原型。它返回指定对象的原型。
const obj = {};
const prototype = Object.getPrototypeOf(obj);
console.log(prototype); // {}
  1. Object.setPrototypeOf(obj, prototype): 用于设置对象的原型。它将指定对象的原型设置为另一个对象或null。
const obj = {};
const prototype = { a: 1 };
Object.setPrototypeOf(obj, prototype);
console.log(obj.a); // 1
  1. __proto__方法:用于获取或设置对象的原型。

正则对象扩展

  1. RegExp构造函数的扩展:在ES6之前,RegExp构造函数不允许使用第二个参数添加修饰符。在ES6中,如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
console.log(new RegExp(/abc/ig, 'i').flags);// "i"
  1. Flags属性:用于返回正则表达式的修饰符。
const regex = /abc/gi;
console.log(regex.flags); // "gi"
  1. u修饰符:用于处理大于\uFFFF的Unicode字符。
console.log(/^\S$/.test('𠮷'));// false
console.log(/^\S$/u.test('𠮷'));// true
  1. y修饰符:也称为“粘连”修饰符,用于指定只从目标字符串的当前位置开始匹配。
const regex = /abc/y;
console.log(regex.exec("abcabc")); // [ 'abc', index: 0, input: 'abcabc', groups: undefined ]
console.log(regex.exec("abcabc")); // [ 'abc', index: 3, input: 'abcabc', groups: undefined ] 因为第二次匹配从上一次匹配的结束位置开始
console.log(regex.exec("abcabc")); // null 
  1. RegExp.prototype.sticky 表示是否有y修饰符
  2. RegExp.prototype.unicode 表示是否有u修饰符
console.log(/hello\d/y.sticky);//true
console.log(/hello\d/u.unicode);//true
  1. 正则方法调用变更:字符串对象的match()replace()search()split()内部调用转为调用RegExp实例对应的RegExp.prototype[Symbol.方法]

ES7

ES7,也称为ECMAScript 2016,是ECMAScript的第7个版本。它于2016年发布,引入了一些新的语言特性和改进。

Array.prototype.includes()方法

  • includes()方法用于判断一个数组是否包含某个指定的元素,并返回布尔值。
let arr = [1,2,3,4,6]
console.log(arr.includes(5));//false
console.log(arr.includes(6));//true

指数操作符

  • ES7引入了指数运算符,用于计算一个数的指数,**具有与 Math.pow(..)等效的计算结果。
console.log(2**3);//8

ES8

ES8,也称为ECMAScript 2017,是ECMAScript(JavaScript)的第八个版本。它于2017年6月发布,并在ES6(ES2015)的基础上引入了一些新特性和语法改进。

async/await

  • ES8引入了async/await语法,它是一种更简洁、更易于理解和编写异步代码的方式。它建立在Promise的基础上,通过使用async关键字来定义一个异步函数,以及使用await关键字来等待一个Promise对象的解决。

  • 使用async关键字定义的函数会返回一个Promise对象,而在函数体内使用await关键字可以暂停函数的执行,直到等待的Promise对象解决为止。这样可以使得异步代码的执行顺序更加线性,避免了回调地狱。

  • 当使用Promise时,链式调用太多的话代码通常会变得比较冗长和嵌套:

// 定义一个延迟函数,接受一个毫秒数作为参数
function delay(ms) {
  // 返回一个新的Promise对象,该对象在指定的毫秒数后解决
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 延迟1秒后执行的操作
delay(1000)
  .then(() => {
    console.log("After 1 second"); // 输出"After 1 second"
    return delay(2000); // 返回一个延迟2秒的Promise对象
  })
  .then(() => {
    console.log("After 2 seconds"); // 输出"After 2 seconds"
    return "Finished"; // 返回一个字符串"Finished"
  })
  .then((result) => {
    console.log(result); // 输出"Finished"
  })
  .catch((error) => {
    console.error(error); // 输出捕获到的错误信息(如果有)
  });
  • 相比之下,使用async/await可以使代码更加线性和易于理解,以一种更接近同步代码的方式编写异步操作:
// 定义一个延迟函数,接受一个毫秒数作为参数
function delay(ms) {
  // 返回一个新的Promise对象,该对象在指定的毫秒数后解决
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 定义一个异步函数example
async function example() {
  console.log("Start"); // 输出"Start"
  await delay(1000); // 等待1秒钟
  console.log("After 1 second"); // 输出"After 1 second"
  await delay(2000); // 等待2秒钟
  console.log("After 2 seconds"); // 输出"After 2 seconds"
  return "Finished"; // 返回一个字符串"Finished"
}

// 定义一个异步函数runExample,用于执行example函数
async function runExample() {
  try {
    const result = await example(); // 等待example函数完成并获取结果
    console.log(result); // 输出example函数的返回值
  } catch (error) {
    console.error(error); // 输出捕获到的错误信息(如果有)
  }
}

runExample(); // 执行runExample函数,开始执行异步操作

Object.entries()

  • Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组
let obj = {a:1,b:2}
for (let [key, value] of Object.entries(obj)) {
    console.log(`${key}: ${value}`)
}
//输出
// a: 1
// b: 2

Object.values()

  • Object.values()方法返回一个给定对象自身可枚举属性值的数组
let obj = {a:1,b:2}
console.log(Object.values(obj));//[ 1, 2 ]

Object.getOwnPropertyDescriptors()

  • Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符
let obj = {a:1,b:2}
console.log(Object.getOwnPropertyDescriptors(obj));
//输出
// {
//     a: { value: 1, writable: true, enumerable: true, configurable: true },
//     b: { value: 2, writable: true, enumerable: true, configurable: true }
// }

padStart()和padEnd()

  • padStart()方法可以在字符串的开头添加指定的字符,直到字符串达到指定的长度。如果字符串的长度已经达到或超过了指定的长度,则不会进行任何填充。
let phoneNumber = "18912345677"; // 电话号码
let paddedPhoneNumber = phoneNumber.slice(-4).padStart(11, "*"); // 将电话号码填充到11位,使用*进行填充,并且只保留后四位数
console.log(paddedPhoneNumber); // *******5677
  • padEnd()方法与padStart()方法类似,不同之处在于它在字符串的结尾添加指定的字符,直到字符串达到指定的长度。
const text = 'Hello';
console.log(text.padEnd(8, '!')); // 输出:'Hello!!!'

ShareArrayBuffer(因安全问题,暂时在Chrome,FireFox,Safari中被禁用)

  • 用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。

ES9

ES9是ECMAScript的第9个版本,也被称为ES2018。它于2018年6月发布,引入了一些新的特性和语法改进。

for await...of

  • for await...of 是 ES9 中引入的一种语法,用于遍历异步迭代器的元素。它的语法类似于传统的 for...of 循环,但可以在异步操作完成后继续迭代下一个元素。

  • 利用Generator函数实现一个异步迭代器对象,然后用for await...of遍历这个异步迭代器的元素:

async function* asyncGenerator() {
  yield Promise.resolve(1);
  yield Promise.resolve(2);
  yield Promise.resolve(3);
}

(async () => {
  for await (const num of asyncGenerator()) {
    console.log(num);
  }
})();

Rest/Spread 属性

  • Rest/Spread 属性语法是对扩展运算符的一种扩展。Rest/Spread 属性允许我们在对象字面量中使用扩展运算符(...)来获取对象的剩余属性或将属性扩展到另一个对象中。

  • Rest 属性用于从对象中提取剩余的属性,并将它们作为新的对象返回。这样可以方便地从一个对象中提取所需的属性,而不必一个一个地进行赋值。:

const { name, age, ...rest } = { name: 'hhh', age: 18, sex: '男', city: '上海' };
console.log(name); // "hhh"
console.log(age); // 18
console.log(rest); // { sex: '男', city: '上海 }
  • Spread 属性用于将一个对象的属性扩展到另一个对象中。这样可以方便地合并两个对象的属性,而不必一个一个地进行赋值。
const obj1 = { name: 'hhh', age: 18 };
const obj2 = { sex: '男', city: '上海' };
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { name: 'hhh', age: 18, sex: '男', city: '上海' }

Promise.finally()

  • Promise结束的时候,不管是结果是resolved还是rejected,都会调用finally中的方法, finally中的回调函数不接受任何参数
const promise = new Promise((resolve, reject) => {
  // 异步操作
  // resolve(value); // 或 reject(reason);
});

promise
  .then(result => {
    // 处理成功的情况
  })
  .catch(error => {
    // 处理失败的情况
  })
  .finally(() => {
    // 无论成功或失败都会执行的回调函数
  });

正则表达式扩展

反向断言

  • 反向断言使用(?<=pattern)的语法来定义,其中pattern是一个子表达式,表示需要存在的模式。它可以用于匹配前面是某种模式的字符串。
const regex = /(?<=\$)\d+/;
const match = regex.exec('The price is $99');
console.log(match[0]); // 输出:99
  • 反向否定断言是一种用于匹配在某种模式之后不存在的字符的方法。它使用(?!pattern)的语法来表示。这个模式会匹配一个位置,这个位置后面的字符不能匹配给定的模式。
const str = "a123bbcd667";
const regex = /(?<![a-z])\d+/g;
const matches = str.match(regex);
console.log(matches); // 输出: [ '23', '67' ]

命名捕获组

  • 命名捕获组允许我们给正则表达式的子表达式(也称为分组)命名,并且可以通过名称来引用它们的匹配结果。

  • 命名捕获组可以通过在子表达式前面加上?<name>的语法来实现。

const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = regex.exec('2023-08-28');

console.log(match.groups.year); // 输出:2023
console.log(match.groups.month); // 输出:08
console.log(match.groups.day); // 输出:28

s修饰符(dotAll模式)

  • 在默认情况下,正则表达式中的点字符(.)匹配除换行符(\n)之外的任何字符。但是,使用 dotAll 模式后,点字符将匹配包括换行符在内的任何字符。
const regex = /a.b/s;
const str = "a\nb";
console.log(regex.test(str)); // 输出 true

Unicode属性转义

  • Unicode 属性转义用于匹配具有特定属性的字符。这些转义的形式分别是 \p{...} 和 \P{...}\p{...} 是一个 Unicode 属性转义,用于匹配具有指定属性的字符。它的语法是 \p{Property=Value},其中 Property 是 Unicode 属性的名称,Value 是该属性的取值。通过使用这个转义,可以在正则表达式中匹配具有特定属性的字符。
const reEmoji = /\p{Emoji}/u;
console.log(reEmoji.test('🥰😊😍')); // 输出 true

ES10

ES10是ECMAScript的第10个版本,也被称为ES2019。它于2019年6月发布,引入了一些新的特性和语法改进。

数组扁平化方法

  • Array.prototype.flat()方法会按照一个可指定的深度遍历递归数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const arr = [[1, 2], [3, [4, 5]]];
console.log(arr.flat(1)); // 输出: [1, 2, 3, [4, 5]]

const deeplyNestedArr = [[1, [2, [3, [4, [5]]]]]];
console.log(deeplyNestedArr.flat(Infinity)); // 输出: [1, 2, 3, 4, 5]
  • Array.prototype.flatMap()方法结合了 map() 和 flat() 两个方法的功能。它首先对数组的每个元素执行一个映射函数,然后将结果扁平化为一维数组。
const arr = [1, 2, 3, 4, 5];
console.log(arr.flatMap(num => [num * num])); // 输出: [1, 4, 9, 16, 25]

const words = ["Hello", "World"];
console.log(words.flatMap(word => word.split(""))); // 输出: ["H", "e", "l", "l", "o", "W", "o", "r", "l", "d"]

字符串去除开头和结尾的空格方法

  • String.prototype.trimStart() 方法去除字符串开头的空白字符,返回一个新的字符串,原始字符串不受影响。空白字符包括空格、制表符、换行符等。
const str = "   Hello, world!   ";
console.log(str.trimStart()); // "Hello, world!   "
  • String.prototype.trimEnd() 方法去除字符串结尾的空白字符,返回一个新的字符串,原始字符串不受影响。
const str = "   Hello, world!   ";
console.log(str.trimEnd()); // "   Hello, world!"

Object.fromEntries

  • Object.fromEntries()用于将一个包含键值对的可迭代对象(如Array,Map等对象)转换为一个新的对象。
const entries = [['name', 'hhh'], ['age', 25], ['city', '上海']];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'hhh', age: 25, city: '上海' }

Symbol.prototype.description

  • Symbol.prototype.description用于获取Symbol的描述信息。
console.log(Symbol('myDescription').description);//myDescription

Function.prototype.toString()

  • 之前的版本中,该方法来自 Object.prototype.toString(),得到的字符串是去掉空白符号的。
  • 但在 ES10 中,得到的字符串会保留空格和注释,如果函数是内置方法的,或者是通过 bind() 方法创建的,Function.prototype.toString() 方法将返回一个标记为 [native code] 的字符串。
function sum(a, b) {
    return a + b;
}

console.log(sum.toString())
// "function sum(a, b) {
// 		return a + b;
//  }"
let newSum = sum.bind();
console.log(newSum.toString());// "function () { [native code] }"
console.log(Math.abs.toString()) // "function abs() { [native code] }"

catch 绑定

  • 在 ES10 中,允许在 catch 语句中绑定错误对象,而不需要通过 catch 语句的参数来引用它。
  • 在 ES10 之前,catch 语句通常如下所示:
try {
  // 一些可能抛出错误的代码
} catch (error) {
  // 处理错误的代码
}

在 ES10 中,可以使用 catch 绑定来进行错误处理,如下所示:

try {
  // 一些可能抛出错误的代码
} catch {
  // 处理错误的代码
}

JSON.stringify() 增强

  • 在 ES10 中,JSON.stringify() 方法修复了之前版本中对于一些超出范围的 Unicode 字符的展示错误的问题。
  • 在修复之前,当遇到超出 U+FFFF 的 Unicode 字符时,JSON.stringify() 方法会将其转义为一个由两个代理字符组成的字符串,而不是正确的 Unicode 字符。
  • 修复后,JSON.stringify() 方法会正确地展示这些超出范围的 Unicode 字符。这个修复使得对于 Unicode 字符的处理更加准确和方便。
console.log(JSON.stringify('🥰')); // "🥰"

ES11

ES11是ECMAScript的第11个版本,也被称为ES2020。它于2020年6月发布,引入了一些新的特性和语法改进。

globalThis

  • 在之前的 JavaScript 版本中,全局对象的名称是不一致的,比如在浏览器中是 window,在 Node.js 中是 global。这导致了在跨平台开发时需要针对不同的环境使用不同的全局对象。

  • 为了解决这个问题,ES11 引入了 globalThis,它是一个统一的全局对象。无论在浏览器还是 Node.js 中,都可以使用 globalThis 来访问全局对象。这样,你就不需要根据不同的环境来手动切换全局对象的名称了。

BigInt

  • 在之前的版本中,整数的范围受到限制,超过 2^53 或小于 -2^53 的整数会丢失精度。而 BigInt 类型通过添加后缀 n 或调用构造函数 BigInt() 来表示超出这个范围的整数,并且可以进行算术运算。

  • 要注意的是:

    • BigIntMath对象中的方法不可用;
    • BigIntNumber实例不能混合运算,需要转换为相同类型;
    • BigInt在转换为Number时可能会丢失精度;
    • 使用BigInt进行带小数的运算会向下取整;
    • BigIntNumber不是严格相等,但是宽松相等。
const maxSafeInteger = BigInt(Number.MAX_SAFE_INTEGER);
const bigNumber = BigInt("123456789012345678901234567890");

console.log(maxSafeInteger); // 9007199254740991n
console.log(bigNumber); // 123456789012345678901234567890n

const sum = maxSafeInteger + bigNumber;
console.log(sum); // 123456789012345678901234576981n

console.log(2n > 2,2n > 1);// false true
console.log(0n === 0,0n == 0);// false true

可选链操作符

  • 可选链操作符(Optional Chaining Operator)用于简化访问嵌套对象属性或方法时的安全性检查。通过?.来判断属性或方法是否存在,如果存在则访问,如果不存在则返回undefined,避免了在访问链中出现的类型错误或未定义错误。

  • 在ES11之前,如果要访问嵌套对象属性或方法时,我们需要手动进行安全性检查,以避免出现错误:

// 在ES11之前的代码
if (obj && obj.prop && obj.prop.method) {
  // 访问obj.prop.method
}
  • 使用可选链操作符,可以简化上述代码,避免了多次的检查和重复的代码。
// 使用可选链操作符
if (obj?.prop?.method) {
  // 访问obj.prop.method
}

空值合并操作符

  • 空值合并操作符(Nullish Coalescing Operator)用于在给定的一组值中选择一个非空(非null和非undefined)的值。

  • 在以前的版本中,我们通常使用逻辑或运算符(||)来实现类似的功能。但是逻辑或运算符有一个问题,它会将假值(例如空字符串''、数字0、布尔值false等)也视为“空”,从而导致错误的结果。

  • 而空值合并操作符(??)只在左侧的值为null或undefined时才会选择右侧的值,对于其他的假值不会触发选择右侧的值。

  • 注意:不可以将 ?? 与 AND(&&)OR(||)混用,会报错。

// 在ES11之前的代码
const value = x || defaultValue;

// 使用空值合并操作符
const value = x ?? defaultValue;

String.prototype.matchAll()

  • String.prototype.matchAll() 返回一个包含所有匹配正则表达式的迭代器对象。迭代器对象可以通过 for...of 循环或者 Array.from() 方法转换为数组,并且每个元素都是一个包含匹配结果的数组。
const str = 'Hello, world!';
const regex = /[a-z]/gi;
const matches = str.matchAll(regex);

for (const match of matches) {
  console.log(match);
}
//输出
// [ 'H', index: 0, input: 'Hello, world!', groups: undefined ]
// [ 'e', index: 1, input: 'Hello, world!', groups: undefined ]
// [ 'l', index: 2, input: 'Hello, world!', groups: undefined ]
// [ 'l', index: 3, input: 'Hello, world!', groups: undefined ]
// [ 'o', index: 4, input: 'Hello, world!', groups: undefined ]
// [ 'w', index: 7, input: 'Hello, world!', groups: undefined ]
// [ 'o', index: 8, input: 'Hello, world!', groups: undefined ]
// [ 'r', index: 9, input: 'Hello, world!', groups: undefined ]
// [ 'l', index: 10, input: 'Hello, world!', groups: undefined ]
// [ 'd', index: 11, input: 'Hello, world!', groups: undefined ]

import()

  • ES11 中引入了 import() 函数,它是一个动态导入模块的方法。在以前的 ES6 模块系统中,所有的导入语句都必须在代码的头部静态编译,不能根据运行时的条件来导入不同的模块。而 import() 函数允许在代码运行时根据需要动态地加载模块。

  • import() 函数返回一个Promise,该 Promise 在模块加载完成后被解析为一个包含模块的默认导出的对象。这使得我们可以在需要的时候延迟加载模块,从而提高应用程序的性能和响应速度。

import('./module.js')
  .then((module) => {
    // 使用模块中的内容
    module.default();
  })
  .catch((error) => {
    // 处理加载模块失败的情况
    console.error('模块加载失败', error);
  });

Promise.allSettled()

  • Promise.allSettled() 方法接收一个由 Promise 对象组成的可迭代对象(比如数组),并返回一个新的 Promise 对象。这个新的 Promise 对象在所有传入的 Promise 对象都已经被解决(settled)后才会被解决。

  • Promise.all() 不同的是,即使其中的某个 Promise 被拒绝(rejected),Promise.allSettled() 仍会等待所有 Promise 对象都被解决,然后返回一个包含每个 Promise 对象结果的数组。这使得我们能够获取所有 Promise 对象的状态,而不必担心其中某个 Promise 对象的拒绝(rejected)会中断整个操作。

const promises = [
  Promise.resolve("Resolved"),
  Promise.reject("Rejected"),
  Promise.resolve("Resolved"),
];

Promise.allSettled(promises).then((results) => {
  results.forEach((result) => {
    if (result.status === "fulfilled") {
      console.log(`Promise resolved: ${result.value}`);
    } else if (result.status === "rejected") {
      console.log(`Promise rejected: ${result.reason}`);
    }
  });
});

ES12

ES12,也被称为ES2021,于2021年6月发布。ES12引入了一些新的特性和改进,以提升JavaScript语言的功能和性能。

数值分隔符

  • 数值分隔符(Numeric Separators)允许在数字中使用下划线 _ 进行分隔,以提高数字的可读性。数值分隔符在 JavaScript 中是可选的,它们不会影响数字的值或计算结果。

  • 使用数值分隔符,可以将长数字分成更易读的部分:

const billion = 1_000_000_000;
console.log(billion); // 输出 1000000000
  • 数值分隔符可以在整数和浮点数中使用,但不能在数字的开头或结尾使用,也不能在小数点前后使用。
const number = 1_234.567_89;
console.log(number); // 输出 1234.56789

逻辑赋值运算符

  • 逻辑赋值运算符(Logical Assignment Operators)是一种简化常见逻辑操作和赋值的合并运算符。

  • ES12中引入了三个逻辑赋值运算符:

  1. ||=:逻辑或赋值运算符。如果左侧的操作数为假(例如,undefinednull、false 或 0),则将右侧的操作数赋值给左侧的变量。
let x = 0;
x ||= 5;
console.log(x); // 输出 5,因为 x 是假值,所以将 5 赋值给 x
  1. &&=:逻辑与赋值运算符。如果左侧的操作数为真(例如,非空字符串、非零数字或对象),则将右侧的操作数赋值给左侧的变量。
let y = 10;
y &&= 7;
console.log(y); // 输出 7,因为 y 是真值,所以将 7 赋值给 y
  1. ??=:空值合并赋值运算符。如果左侧的操作数为 nullundefined,则将右侧的操作数赋值给左侧的变量。
let z = null;
z ??= 3;
console.log(z); // 输出 3,因为 z 是空值,所以将 3 赋值给 z

String.prototype.replaceAll()

  • String.prototype.replaceAll()方法,该方法可以用指定的字符串替换字符串中的所有匹配项。在之前的版本中,我们只能使用正则表达式配合replace()方法来实现替换所有匹配项的操作。

  • 使用 replaceAll() 方法非常简便,只需要传入两个参数:第一个参数是要替换的字符串或正则表达式(必须是全局的),第二个参数是替换后的字符串。方法会返回一个新的字符串,其中所有匹配项都被替换为指定的字符串。

console.log("Hello, world!".replaceAll("o", "x")); // "Hellx, wxrld!"
console.log("Hello, world!".replaceAll(/l/g, "x")); // "Hexxo, worxd!"

Promise.any()

  • Promise.any() 方法接收一个 Promise 对象的可迭代参数(如数组),并返回一个新的 Promise 对象。

  • 这个新的 Promise 对象将在其中的任何一个 Promise 对象变为 fulfilled(已完成)状态时解析,并以该 Promise 对象的解析值作为其解析值。如果可迭代参数中的所有 Promise 对象都变为 rejected(已拒绝)状态,则返回的 Promise 对象将会被拒绝,并以一个 AggregateError 实例作为拒绝值。

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(2);
const promise3 = Promise.resolve(1);

Promise.any([promise1, promise2, promise3])
  .then((value) => {
    console.log(value); // 1
  })
  .catch((error) => {
    // 全部请求失败时才会执行这里的代码
    console.log(error);
  });

小结

以上是我整理的ES6到ES12的常用新特性,本人水平有限,如有错误欢迎在评论区指正,一起讨论!(๑•̀ㅂ•́)و✧