JavaScript 新特性 学习笔记

152 阅读17分钟

前言

ES6 是 ECMAScript 6.0 版本的简称,正式名称为 ECMAScript 2015(ES2015),其是 JavaScript 语言的下一代标准,于2015年6月发布‌。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

ES6 是一个历史名词,也是一个泛指,涵盖了 ES2016、ES2017、ES2018、ES2019 和 ES2020等等所有版本。ES6 的标准每年6月发布,并作为当年的正式版本。例如,ES2016、ES2017、ES2018、ES2019 和 ES2020 都是在每年的6月发布的‌。因此,ES6 并不特指2016年发布的版本,而是指从ES2015 开始的一系列标准‌。

其中,对于一些老旧浏览器只支持ES5规范的,需要对ES6规范进行转码提高代码的兼容性,此过程中运用较为普遍的转码器为Babel。

本文仅描述部分各年更新的新特性,详情请参考官网及阮一峰老师的《ECMAScript6》

ES2015 新特性

let 和 const 关键字

  • 不存在变量提升。let声明具有块级作用域效果,不会和var声明一样做变量提升并具有全局作用域效果,示例代码如下:
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
  • 暂时性死区。let声明存在暂时性死区,原因感觉也可归咎于let不存在变量提升的问题,示例如下:
// example 1
var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

// example 2
function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 报错

// example 3
// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError: x is not defined

比较有趣的对比示例:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6]();  // 10

for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6]();  // 6

箭头函数

() => { }是允许使用‘箭头’(=>)来定义函数。

  • 注意点

    1. 箭头函数没有自己的 this 对象,其指向的是上级作用域。同时因为没有自己的 this 对象,箭头函数不能通过 call()apply()bind() 这些方法去改变 this 指向。
    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    
    var id = 21;
    
    foo.call({ id: 42 });
    // id: 42
    
    1. 不可以当作构造函数,也就是说,不可以对箭头函数使用 new 命令,否则会抛出一个错误。
    2. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    3. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

模板字符串

字符串拼接,从原始的 str1 + str2 方式,通过字符串模板可以改写为 `${str1}${str2}`

默认参数

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

解构赋值

对于数组、对象、字符串、数值及boolean值,还有函数的参数都能够使用结构赋值。

// ----------- 数组 start -----------
let [head, ...tail] = [1, 2, 3, 4];  // head=1,  tail=[2, 3, 4]

let [foo] = [];  // undefined
let [bar, foo] = [1];  // undefined

// 报错: 等号的右边不是数组(或者严格地说,不是可遍历的结构)
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

// 数组的解构赋值允许指定默认值
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
// ----------- 数组 end -----------

// ----------- 对象 start -----------
// 对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };  // foo="aaa", bar="bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };  // baz=undefined
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };  // baz="aaa"

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

// 注意解构对象时不写第一个p,此处会不对p进行定义赋值,p:仅为模式而非变量。
let { p, p: [x, { y }] } = obj; // x="Hello", y="World", p=["Hello", {y: "World"}]

// 对象的解构赋值允许指定默认值
// 默认值生效的条件是,对象的属性值严格等于undefined。
var {x = 3} = {x: undefined};  // x=3
var {x = 3} = {x: null};  // x=null
// ----------- 对象 end -----------

// ----------- 字符串 start -----------
const [a, b, c, d, e] = 'hello';  // a="h", b="e", c="l", d="l", e="o"
let {length : len} = 'hello';  // len=5, 对属性进行结构赋值
// ----------- 字符串 end -----------

// ----------- 数值及boolean值 start -----------
// 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true

// 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
// ----------- 数值及boolean值 end -----------

// ----------- 函数参数 start -----------
[[1, 2], [3, 4]].map(([a, b]) => a + b);  // [ 3, 7 ]

// 函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
// ----------- 函数参数 end -----------

模块化

使用import取代require(),使用export取代module.exports

// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import { func1, func2 } from 'moduleA';

// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
  render() {
    return <nav />;
  }
});
module.exports = Breadcrumbs;

// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
  render() {
    return <nav />;
  }
};
export default Breadcrumbs;

类(Class)

用更简洁更易于理解的类写法取代需要编写原型链 prototype 的操作。并且可以通过 extends 实现继承,在操作上更简单且不会有破坏 instanceof 运算的危险。

class Queue {
  constructor(contents = []) {
    this._queue = [...contents];
  }
  pop() {
    const value = this._queue[0];
    this._queue.splice(0, 1);
    return value;
  }
}

class PeekableQueue extends Queue {
  peek() {
    return this._queue[0];
  }
}

Promise

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。基本用法如下:

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

Promise下有许多属性方法,包括

  1. Promise.prototype.then()
  2. Promise.prototype.catch()
  3. Promise.prototype.finally()
  4. Promise.all()
  5. Promise.race()
  6. Promise.allSettled()
  7. Promise.any()
  8. Promise.resolve()
  9. Promise.reject()
  10. Promise.try()

应用在封装Ajax的场景:

// 原来的方式
const btn = document.querySelector("#btn");
btn.addEventListener('click', function(){
  // 1.创建对象
  const xhr = new XMLHttpRequest();
  // 2.初始化
  xhr.open('GET', 'https://api.apiopen.top/getJoke');
  // 3.发送
  xhr.send();
  // 4. 处理响应结果
  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4) {
      // 判断响应状态码 2xx
      if(xhr.status >= 200 && xhr.status < 300) {
        // 输出响应体
        console.log(xhr.response);
      } else {  
        // 输出响应状态码
        console.log(xhr.status);
      }
    }
  }
})

// 使用Promise
const btn = document.querySelector('#btn');
btn.addEventListener('click', function(){
  const p = new Promise((resolve, reject) => {
    // 1、创建对象 
    const xhr = new XMLHttpRequest();
    // 2、初始化
    xhr.open('GET', 'https://api.apiopen.top/getJok');
    // 3、发送
    xhr.send();
    // 4、处理响应结果
    xhr.onreadystatechange = function(){
      // 判断状态
      if(xhr.readyState === 4){
          if(xhr.status >= 200 && xhr.status < 300){
            resolve(xhr.response);
          }else{
            reject(xhr.status);
          }
        }
    }
  })
});

p.then(value=>{
  console.log(value);
}, reason=>{
  console.warn();
})

其他新特性

扩展运算符...、Symbol类型、迭代器(Iterator)、生成器(Generator)、代理(Proxy)和反射(Reflect)等‌。

ES2016 新特性

Array.prototype.includes

Arr.includes(item) 方法判断数组Arr中是否含有特定值item。

指数运算符

幂运算符(**):返回第一个操作数取第二个操作数的幂运算结果。等价于 Math.pow() ,但不同之处在于幂运算符可接受BigInt类型的操作数。

console.log((2 ** 3) ** 2);               // output: 64
console.log(Math.pow(Math.pow(2, 3), 3))  // output: 64

ES2017 新特性

异步函数

异步函数使用 async 关键字声明,允许在函数内部使用 await 关键字来等待 Promise 的执行完毕。这使得异步代码的编写更加简洁和直观,避免了回调地狱的问题‌。

Object.getOwnPropertyDescriptors

该方法返回指定对象所有自身属性的描述对象。

const object1 = {
  property1: 42,
  property2: 'hello'
};
 
const property1Descriptor = Object.getOwnPropertyDescriptor(object1, 'property1');
const property2Descriptor = Object.getOwnPropertyDescriptor(object1, 'property2');
 
console.log(property1Descriptor);
// { value: 42, writable: true, enumerable: true, configurable: true }
console.log(property2Descriptor);
// { value: 'hello', writable: true, enumerable: true, configurable: true }

通过该方法可以实现深拷贝:

const object1 = {
  property1: 42,
  property2: {
    property3: 'hello'
  }
};
 
const object2 = Object.create(Object.getPrototypeOf(object1), Object.getOwnPropertyDescriptors(object1));
 
console.log(object2);
// { property1: 42, property2: { property3: 'hello' } }

Object.entries 和 Object.values

作用类似于 Object.keys() ,区别在于如下代码反馈:

const values = { a: 1, b: 2, c: 3 };

console.log(values.keys())     // output: [1, 2, 3]
console.log(values.values())   // output: ['a', 'b', 'c']
console.log(values.entries())  // output: [['a', 1], ['b', 2], ['c', 3]]

String.prototype.padStart 和 String.prototype.padEnd

这两个方法分别用于在字符串的开始和结束填充指定的字符,直到达到指定的长度。padStart用于在字符串开头填充,而padEnd用于在字符串末尾填充‌。

String.prototype.padStartString.prototype.padEnd 方法默认参数为(padLength, padString=' ')。

  • padLength 是填充后的结果字符串的长度。如果 padLength 小于字符串的长度,则字符串按原样返回,没有填充。
  • padString 是一个可选参数,用于填充字符串。此参数的默认值为“ ”。如果 padString 大于 padLength,padString 将被截断,只填充最左边的部分。
let str = '1234'.padStart(8, '0');
console.log(str); // "00001234"

str = 'abc'.padStart(5);
console.log(str); // "  abc"

str = 'abc'.padEnd(5, 'def');
console.log(str); // "abcde"

str = 'abc'.padEnd(2, 'def');
console.log(str); // "abc"

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

ES2017允许在函数参数列表和调用中使用尾随逗号,这提高了代码的可读性和维护性‌。

ES2018 新特性

Async Iteration

异步生成器函数添加遍历器。

async function* numbers() {
  for (let i = 0; i < 3; i++) {
    yield i;
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}
 
(async () => {
  for await (let number of numbers()) {
    console.log(number);
  }
})();

Promise.finally()

Promise.finally() 方法来指定最终要执行的回调函数,无论 Promise 对象最后的状态如何,这个回调函数都会被调用。

Promise.resolve()
  .then(() => console.log('Success'))
  .catch(() => console.log('Error'))
  .finally(() => console.log('Finished'));

Rest/Spread Properties

类似反作用于解构赋值,可将参数拆开后分组赋值给新变量。示例代码如下:

const values = { a: 1, b: 2, c: 3 };
const { a, ...rest } = values;

console.log(a); // 1
console.log(rest); // { b: 2, c: 3 }

ES2019 新特性

可选的catch绑定

对于不处理报错信息的可以不绑定参数从原来的 try { ... } catch(err) { ... } 变为可选择性的 try{ ... } catch { ... }

Object.fromEntries

类似于Object.entries方法的反作用。接受一个键值对的列表参数,并返回一个带有这些键值对的新对象。

let leo = { name: 'pingan8787', age: 10};
let arr = Object.entries(leo);
console.log(arr);// [["name", "pingan8787"],["age", 10]]

let obj = Object.fromEntries(arr);
console.log(obj);// {name: "pingan8787", age: 10}

Array.prototype.flat 及 Array.prototype.flatMap

flat的作用是将数组的第一层展平,flatMap是加了展平作用的map方法,示例如下:

[1, 2, 3, [1, 2, [3, [4]]]].flat(2);
// [1, 2, 3, 1, 2, 3, [4]]

[1,3,5].map(x => [x * x]); // [[1], [9], [25]]
[1,3,5].flatMap(x => [x * x]); // [1, 9, 25]

String.prototype.trimStart 及 String.prototype.trimEnd

该两个方法分别是从字符串的开头和结尾删除空格并返回一个新字符串。

let pingan8787 = '   Hello pingan8787!   ';
console.log(pingan8787);        // "   Hello pingan8787!   ";
console.log(pingan8787.length); // 23;

console.log(pingan8787.trimStart());        // "Hello pingan8787!   ";
console.log(pingan8787.trimStart().length); // 20;

console.log(pingan8787.trimEnd());        // "Hello pingan8787!";
console.log(pingan8787.trimEnd().length); // 17;

ES2020 新特性

‌String.prototype.matchAll‌

字符串正则匹配扩展,该方法会返回全部匹配结果的一个迭代器。matchAll 解决的就是,既能匹配出所有项,也能得到每项的详细信息,例如group的匹配:

var str = '<text>JS</text><text>正则</text>';
var allMatchs = str.matchAll(/<\w+>(.*?)<\/\w+>/g);
for(const match of allMatchs) {
  console.log(match);
}
/*
第一次迭代返回:
[
    "<text>JS</text>",
    "JS",
    index: 0,
    input: "<text>JS</text><text>正则</text>",
    groups: undefined
]
第二次迭代返回:
[
    "<text>正则</text>",
    "正则",
    index: 15,
    input: "<text>JS</text><text>正则</text>",
    groups: undefined
]
*/

动态导入

动态的import一个模块,返回一个promise,可以在module加载完成后做一些事情,或者捕获错误

el.onclick = () => {
    import(`/path/current-logic.js`)
    .then((module) => {
        module.doSomthing();
    })
    .catch((err) => {
        // load error;
    })
}

‌BigInt

新的数据类型:BigInt。以下是BigInt的使用方法:

//在整数字面量后面加n。
var bigIntNum = 9007199254740993n;

//使用 BigInt 函数。
var bigIntNum = BigInt(9007199254740);
var anOtherBigIntNum = BigInt('9007199254740993');

//通过 BigInt, 我们可以安全的进行大数整型计算
var bigNumRet = 9007199254740993n+ 9007199254740993n; // -> -> 18014398509481986n
bigNumRet.toString(); // -> '18014398509481986'

‌Promise.allSettled

Promise.all的缺陷是,有一个任务reject,所有的任务都挂掉,这通常不是我们想要的结果。

Promise.allSettled,就是为了解决这个问题。它执行完后,返回所有的执行结果,无论是resolve还是reject,在放在一个数组里返回。

Promise.allSettled([
    Promise.reject({code: 500, msg: '服务异常'}),
    Promise.resolve({ code: 200, list: []}),
    Promise.resolve({code: 200, list: []})
])
.then((ret) => {
    /*
        0: {status: "rejected", reason: {…}}
        1: {status: "fulfilled", value: {…}}
        2: {status: "fulfilled", value: {…}}
    */

    // 过滤掉 rejected 状态,尽可能多的保证页面区域数据渲染
    RenderContent(ret.filter((el) => {
        return el.status !== 'rejected';
    }));
})

Optional Chaining(可选链)‌

使用 ?. 操作符来安全地访问嵌套对象属性,避免因属性不存在而导致的错误。同时也可运用在没有实现的方法上做判断。

const values = { a: 1, b: 2, c: 3 };

console.log(values?.a); // 1
console.log(values?.d); // undefined

// 判断对象方法是否存在,如果存在就立即执行
iterator.return?.()

空值运算符(??)

空值操作符(??)可以避开null和undefined,得到一个默认值。相比于||操作,对0、NaN等值不会转化为false,只处理null和undefined,规避了一些有效值。

const foo = null ?? 'default'
//foo: 'default'

const baz = 0 ?? 12
//baz: 0

ES2021 新特性

String.prototype.replaceAll()

此方法允许开发者替换字符串中所有匹配的子字符串,而无需使用复杂的正则表达式。这简化了字符串的批量替换操作。

const myString = "CatCat";
const newString = myString.replaceAll("Cat", "Dog"); //  "DogDog"

逻辑赋值运算符(Logical Assignment Operators)

新增的逻辑赋值运算符包括:&&=||=??=,这些运算符结婚了逻辑运算符和赋值表达式,提供了更简洁的条件赋值方式。

a &&= b;  // 等价于 if (a) { a = b; };
a ||= b;  // 等价于 if (!a) { a = b; };
a ??= b;  // 等价于 if (a === null || a === undefined) { a = b; };

数字分隔符(Numeric Separators):

允许在数字中使用下划线_作为分隔符,以提高长数字的可读性。分隔符不能位于数字的开头或结尾,且相邻的分隔符之间至少需要一个数字。例如:const oneMillion = 1_000_000; 更易于阅读。

Promise.any()

此方法接收一个Promise对象的数组,并在其中任何一个Promise成功时返回该Promise的结果。如果所有Promise都失败,则抛出一个AggregateError异常。

这在处理多个并行的异步操作并只需要其中一个成功结果时非常有用。

Promise.any([
    Promise.reject({code: 500, msg: '服务异常'}),
    Promise.resolve({ code: 200, list: []}),
    Promise.resolve({code: 200, list: []})
]) // {status: "fulfilled", value: {…}}

WeakRefs

允许创建对对象的弱引用,这意味着当对象没有其他强引用时,垃圾回收器可以回收该对象,而WeakRef不会阻止这一行为。

WeakRefs.deref方法,当引用对象存在时,返回对象实例,当引用对象不存在时,该方法返回undefined。

let obj = { name: 'Lazy' }
const ref = new WeakRef(obj);
let obj1 = ref.deref();
if (obj1) {
  console.log(obj.name); // Lazy
}

FinalizationRegistry

提供了一种在对象被垃圾回收时执行清理操作(回调函数)的机制。FinalizationRegistry对象可以让你在对象被垃圾回收时请求一个回调,这个回调函数通常被称为“finalizer”‌。

const finalRegistry = new FinalizationRegistry((value) => {
    console.log("对象被垃圾回收时触发, value 为 register 方法的第二个参数, 不传的话为 undefined", value);
});
let a = {name: "a"};
let b = {age: 18};
let c = {hobby: "game"};
finalRegistry.register(a); // 注册对象a
finalRegistry.register(b, "this is b"); // 第二个参数会被当成回调函数的参数
finalRegistry.register(c, "this is c", c); // 第三个参数用于取消监听
a = null; // 打印 undefined
b = null; // 打印 this is b
finalRegistry.unregister(c); // 不会打印
c = null;

ES2022 新特性

Array.prototype.at

长久以来,JavaScript 不支持数组的负索引,如果要引用数组的最后一个成员,不能写成arr[-1],只能使用arr[arr.length - 1]。

为了解决这个问题,ES2022 为数组实例增加了at()方法,接受一个整数作为参数,返回对应位置的成员,并支持负索引。这个方法不仅可用于数组,也可用于字符串和类型数组(TypedArray)。

const arr = [5, 12, 8, 130, 44];
arr.at(2) // 8
arr.at(-2) // 130

const sentence = 'This is a sample sentence';
sentence.at(0); // 'T'
sentence.at(-1); // 'e'
sentence.at(-100) // undefined
sentence.at(100) // undefined

Object.hasOwn()

该方法提供了一种新的方式来检查对象是否具有特定的自身属性(而不是继承的属性)。它类似于 Object.prototype.hasOwnProperty(),但更简洁和直观。

const object1 = {
  prop: 'exists',
};

console.log(Object.hasOwn(object1, 'prop')); // true
console.log(Object.hasOwn(object1, 'toString')); // false
console.log(Object.hasOwn(object1, 'undeclaredPropertyValue')); // false

正则表达式匹配索引(RegExp Match Indices)

为正则表达式引入了 d 标志,用于捕获匹配的索引。使用 d 标志后,匹配对象将包含 indices 属性,该属性提供了每个匹配项的开始和结束位置。

const regex = /foo/d;
const match = regex.exec('foo bar foo');
console.log(match.indices);  // [[0, 3]]

类字段和私有方法(Class Fields and Private Methods)

引入了类字段和私有方法,使类的定义更加简洁和安全。私有字段和方法使用 # 前缀定义,只能在类的内部访问。

class Person {
  #name;
  
  constructor(name) {
    this.#name = name;
  }

  #getName() {
    return this.#name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#getName()}`);
  }
}

const alice = new Person('Alice');
alice.greet(); // "Hello, my name is Alice"

Array.prototype.findLast 和 Array.prototype.findLastIndex

用于从数组末尾开始查找满足条件的元素,类似于现有的 find 和 findIndex 方法,但查找方向相反。

const arr = [1, 2, 3, 4, 5];

const result = arr.findLast(element => element % 2 === 0);
console.log(result); // 4
const index = arr.findLastIndex(element => element % 2 === 0);
console.log(index); // 3

ES2023 新特性

Array.prototype.toReversed、Array.prototype.toSorted、Array.prototype.toSpliced 和 Array.prototype.with

  • toReversed: 返回一个新数组,其中的元素顺序与原数组相反,不改变原数组
  • toSorted: 返回一个新的已排序的数组,不改变原数组
  • toSpliced: 返回一个新数组,在给定的索引处删除和/或替换了一些元素,不改变原数组
  • with: 返回一个新数组,在指定索引位置替换一个元素,不改变原数组
const array = [1, 2, 3];
const reversedArray = array.toReversed();    // [3, 2, 1]
const newArray = array.with(1, 10);          // [1, 10, 3]
const sortedArray = newArray.toSorted();     // [1, 3, 10]
const splicedArray = array.toSpliced(1, 2);  // [1]

ES2024 新特性

Promise.withResolvers()

用于生成待定状态 Promise 实例的语法糖。有时候我们需要把 Promise 的 resolve 或者 reject 这两个参数给取出来,然后在觉得适当的时机去执行这个 resolve,这样 Promise 就变 fullfiled 了。

// 2024 之前,我们可以通过 作用域提升 来“曲线救国”
let resolve, reject
const promise = new Promise((res, rej) => {
  resolve = res
  reject = rej
})
true ? resolve('1') : reject('0')

// 现在:一步生成实例和冻态函数
const { promise, resolve, reject } = Promise.withResolvers()
true ? resolve('1') : reject('0')

Object.groupBy() 及 Map.groupBy()

该方法可以基于传递的回调函数返回的字符串,对给定对象中的元素分组。Map和Object调用的区别就在于返回值一个是Object类型一个是Map类型。

const fans = [
  { name: '用户1', type: '类型1' },
  { name: '用户3', type: '类型2' },
  { name: '用户2', type: '类型1' },
  { name: '用户4', type: '类型2' }
]
const result1 = Object.groupBy(fans, ({ type }) => type)
const result2 = Map.groupBy(fans, ({ type }) => type)
console.log(result1)
// {
//  "类型1": [{ name: '用户1', type: '类型1' }, { name: '用户2', type: '类型1' }],
//  "类型2": [{ name: '用户3', type: '类型2' }, { name: '用户4', type: '类型2' }]
// }
console.log(result2)
// [
//  0: { '类型1' => [{ name: '用户1', type: '类型1' }, { name: '用户2', type: '类型1' }] },
//  1: { '类型2' => [{ name: '用户3', type: '类型2' }, { name: '用户4', type: '类型2' }] }
// ]

String.prototype.toWellFormed() 及 String.prototype.isWellFormed()

  • String.prototype.toWellFormed()用于将字符串所有错误的单独代理项替换为正确格式的 Unicode 字符。这是一个纯函数方法,即该方法会返回一个全新的字符串。
  • String.prototype.isWellFormed()用于判定字符串是否格式正确。
const url = 'https://bilibili.com/search?q=\uD800'
encodeURI(url.toWellFormed())
// "https://bilibili.com/search?q=%EF%BF%BD"
// 次处为转换过后替换为正确格式的 Unicode 字符,而非乱码

url.isWellFormed()                 // false,格式错误
url.toWellFormed().isWellFormed()  // true,格式正确