最近,看了Erick Wendel的预测,我看了下一些数组更新方法,抛出来的一些更新基本都已经进入了3 or 4阶段,如果不出意外的话(基本不会出什么意外的吧),那es2023新出的api是稳了。我个人认为这提案对我们理解代码的思维和角度有很大的帮助,比如破坏性和非破坏性,数组分组,fromAsync等(求求了慢点更新吧,2022都没玩熟呢)。那下面先来看看一些小知识。

ECMAScript 规范每年都会更新一次,正式标准化 JavaScript 语言的 ECMAScript 的下一次年度更新将在 2023年 6 月左右获得批准,这将是 ECMAScript 的第 14 版。所有在 2023 年 3 月之前达到阶段 4 的提案都将包含在ECMAScript 2023 标准中。对于一个提案,从提出到最后被纳入 ECMAScript 标准,总共分为五步:

  1. stage0(strawman):任何TC39的成员都可以提交。
  2. stage1(proposal):进入此阶段就意味着这一提案被认为是正式的了,需要对此提案的场景与API进行详尽的描述。
  3. stage2(draft):演进到这一阶段的提案如果能最终进入到标准,那么在之后的阶段都不会有太大的变化,因为理论上只接受增量修改。
  4. state3(candidate):这一阶段的提案只有在遇到了重大问题才会修改,规范文档需要被全面的完成。
  5. state4(finished):这一阶段的提案将会被纳入到ES每年发布的规范之中。

根据 Erick Wendel(微软 MVP、谷歌开发专家、@nodejs合作者)的预测,ECMAScript 2023 可能会新增以下数组方法(序号为所处提案阶段):
3️⃣ Array.prototype.toReversed()
3️⃣ Array.prototype.toSorted()
3️⃣ Array.prototype.toSpliced()
3️⃣ Array.prototype.with()
3️⃣ Array.prototype.group()
3️⃣ Array.prototype.groupToMap()
4️⃣ Array.prototype.findLast()
4️⃣ Array.prototype.findLastIndex()
3️⃣ Array.fromAsync()

下面就来看看这些方法是如何使用的吧!

1. 通过副本更改数组

通过副本更改数组的提案目前处于第 3 阶段。该提案为数组和类型化数组提出了四种新的方法:
1.Array.prototype.toReversed()
2.Array.prototype.toSorted()
3.Array.prototype.toSpliced()
4.Array.prototype.with()

为什么会有这个提案呢?
我们知道,大多数的数组方法都是非破坏性的,也就是说,在数组执行该方法时,不会改变原数组,比如 filter() 方法:

const arr = ['a', 'b', 'b', 'a'];
const result = arr.filter(x => x !== 'b');
console.log(result); // ['a', 'a']

但是,也有一些是破坏性的方法,它们在执行时会改变原数组,比如 sort() 方法:

const arr = ['c', 'a', 'b'];
const result = arr.sort();
console.log(result); // ['a', 'b', 'c']

在数组的方法中,下面的方法是具有破坏性的:
reverse()
sort()
splice()

如果我们想要这些数组方法应用于数组而不改变它,可以使用下面任意一种形式:

const sorted1 = arr.slice().sort();
const sorted2 = [...arr].sort();
const sorted3 = Array.from(arr).sort();

可以看到,我们首先需要创建数组的副本,再对这个副本进行修改,否则就会对原数组进行破坏。
因此改提案就引入了这三个方法的非破坏性版本,因此不需要手动创建副本再进行操作:
reverse() 的非破坏性版本:toReversed()
sort() 非破坏性版本:toSorted(compareFn)
splice() 非破坏性版本:toSpliced(start, deleteCount, …items)
该提案将这些函数属性引入到 Array.prototype(加到对象的原型内 ):

Array.prototype.toReversed() -> Array
Array.prototype.toSorted(compareFn) -> Array
Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
Array.prototype.with(index, value) -> Array

除此之外,该提案还还提出了一个新的非破坏性方法:with()。该方法会以非破坏性的方式替换给定 index 处的数组元素,即 arr[index]=value 的非破坏性版本。
所有这些方法都将保持目标数组不变,并返回它的副本并执行更改。这些方法适用于数组,也适用于类型化数组。
下面就具体说下这几个数组方法吧

1.Array.prototype.toReversed()

//toReversed() 是 reverse() 方法的非破坏性版本:
const arr = ['a', 'b', 'c'];
console.log(arr); // ['a', 'b', 'c']
//new - 非破坏性
const newNotDestruction = arr.toReversed();
console.log(newNotDestruction); // ['c', 'b', 'a']
//old - 非破坏性
const oldNotDestruction = Array.from(arr).reverse();
console.log(oldNotDestruction); // ['c', 'b', 'a']
//old - 破坏性
arr.reverse();
console.log(arr); // ['c', 'b', 'a']

//下面是 toReversed() 方法的一个简单的实现:
if (!Array.prototype.toReversed) {
  Array.prototype.toReversed = function () {
    return this.slice().reverse();
  };
}

2.Array.prototype.toSorted()

//toSorted() 是 sort() 方法的非破坏性版本:
const arr = ['c', 'a', 'b'];
console.log(arr); // ['c', 'a', 'b']
//new - 非破坏性
const newNotDestruction = arr.toSorted();
console.log(newNotDestruction); // ['a', 'b', 'c']
//old - 非破坏性
const oldNotDestruction = Array.from(arr).toSorted();
console.log(oldNotDestruction); // ['a', 'b', 'c']
//old - 破坏性
arr.sort();
console.log(arr); // ['a', 'b', 'c']

//下面是 toSorted() 方法的一个简单的实现:
if (!Array.prototype.toSorted) {
  Array.prototype.toSorted = function (compareFn) {
    return this.slice().sort(compareFn);
  };
}

3.Array.prototype.toSpliced()

splice() 方法比其他几种方法都复杂,其使用形式:splice(start, deleteCount, …items)。该方法会从从 start 索引处开始删除 deleteCount个元素,然后在 start 索引处开始插入item 中的元素,最后返回已经删除的元素。
toSpliced 是 splice() 方法的非破坏性版本,它会返回更新后的数组,原数组不会变化,并且我们无法再得到已经删除的元素:

const arr = ['a', 'b', 'c', 'd'];

console.log(arr); // ['a', 'b', 'c', 'd']
//new - 非破坏性
const newNotDestruction = arr.toSpliced(1, 2, 'X');
console.log(newNotDestruction); // ['a', 'X', 'd']
//old - 非破坏性
const oldNotDestruction = Array.from(arr).splice(1, 2, 'X');
console.log(oldNotDestruction); // ['a', 'X', 'd']
//old - 破坏性
arr.splice(1, 2, 'X');
console.log(arr); // ['a', 'X', 'd']

//下面是 toSpliced() 方法的一个简单的实现:
if (!Array.prototype.toSpliced) {
  Array.prototype.toSpliced = function (start, deleteCount, ...items) {
    const copy = this.slice();
    copy.splice(start, deleteCount, ...items);
    return copy;
  };
}

4.Array.prototype.with()

//with()方法的使用形式:with(index, value),它是 arr[index] = value 的非破坏性版本。
//不得不说 这也能想到 =。=
const arr = ['a', 'b', 'c'];
console.log(arr); // ['a', 'b', 'c']
//new - 非破坏性
const newNotDestruction = arr.with(1, 'X');
console.log(newNotDestruction); // ['a', 'X', 'c']
//old - 非破坏性
const oldNotDestruction = [...arr][1] = 'X';
console.log(oldNotDestruction); // ['a', 'X', 'c']
//old - 破坏性
arr[1] = 'X';
console.log(arr); // ['a', 'X', 'c']

//下面是 with() 方法的一个简单的实现:
if (!Array.prototype.with) {
  Array.prototype.with = function (index, value) {
    const copy = this.slice();
    copy[index] = value;
    return copy;
  };
}

2. 数组分组

1.概述
在日常开发中,数组分组是一种极其常见的操作。因此,就提出了两个新的数组方法:
array.group(callback, thisArg?)
array.groupToMap(callback, thisArg?)

提案地址:https://github.com/tc39/proposal-array-grouping 下面是这两个方法的类型签名(但是用到symbol这是我看不懂的地方):

Array<Elem>.prototype.group<GroupKey extends (string|symbol)>(
  callback: (value: Elem, index: number, array: Array<Elem>) => GroupKey,
  thisArg?: any
): {[k: GroupKey]: Array<Elem>}

Array<Elem>.prototype.groupToMap<GroupKey>(
  callback: (value: Elem, index: number, array: Array<Elem>) => GroupKey,
  thisArg?: any
): Map<GroupKey, Array<Elem>>

这两个方法都用来对数组进行分组(这个思路真的绝):
输入:一个数组;
输出:组,每个组都有一个组key,以及一个包含组成员的数组。
这两个方法都会对数组进行遍历,它们会向其回调请求组键并将元素添加到相应的组中。这两个方法在表示组的方式上有所不同:
group():将组存储在对象中:组键存储为属性键,组成员存储为属性值;
groupToMap():将组存储在 Map 中:组键存储为 Map 键,组成员存储为 Map 值。
那这两个方法该如何选择呢?我们知道,JavaScript 中对象是支持解构的,如果想要使用解构来获取数组中的值,比如,对于上面对象,可以通过解构获取三个不同组的值:

const {vegetables, fruit, meat} = result;

而 Map 的好处就是它的 key 不限于字符串和symbol,更加自由。

2.使用
可能看的有点绕,那下面来看几个实用例子。假如执行Promise.allSettled() 方法返回的数组如下:

const settled = [
  { status: 'rejected', reason: 'Jhon' },
  { status: 'fulfilled', value: 'Jane' },
  { status: 'fulfilled', value: 'John' },
  { status: 'rejected', reason: 'Jaen' },
  { status: 'rejected', reason: 'Jnoh' },
];

const {fulfilled, rejected} = settled.group(x => x.status);

// fulfilled 结果如下:
[
  { status: 'fulfilled', value: 'Jane' },
  { status: 'fulfilled', value: 'John' },
]

// rejected 结果如下:
[
  { status: 'rejected', reason: 'Jhon' },
  { status: 'rejected', reason: 'Jaen' },
  { status: 'rejected', reason: 'Jnoh' },
]
在这个例子中,使用 group() 的效果会更好,因为可以使用解构轻松获取需要组的值。
假如想要对以下数组中人根据国家进行分组:
const persons = [
  { name: 'Louise', country: 'France' },
  { name: 'Felix', country: 'Germany' },
  { name: 'Ava', country: 'USA' },
  { name: 'Léo', country: 'France' },
  { name: 'Oliver', country: 'USA' },
  { name: 'Leni', country: 'Germany' },
];

const result = persons.groupToMap((person) => person.country);

// result 的执行结果和以下 Map 是等价的:
new Map([
  [
    'France',
    [
      { name: 'Louise', country: 'France' },
      { name: 'Léo', country: 'France' },
    ]
  ],
  [
    'Germany',
    [
      { name: 'Felix', country: 'Germany' },
      { name: 'Leni', country: 'Germany' },
    ]
  ],
  [
    'USA',
    [
      { name: 'Ava', country: 'USA' },
      { name: 'Oliver', country: 'USA' },
    ]
  ],
])

在这个例子中,groupToMap() 是更好的选择,因为我们可以在Map 中使用任何类型的键,而在对象中,键只能是字符串或symbol。
3.实现:实现就不讲了,我还在研究,代码有点多,消化不下去=。=

3. 从尾到头搜索数组

1.概述
在 JavaScript 中,通过 find() 和 findIndex() 查找数组中的值是一种常见做法。不过,这些方法从数组的开始进行遍历:

const array = [{v: 1}, {v: 2}, {v: 3}, {v: 4}, {v: 5}];

array.find(elem => elem.v > 3); // {v: 4}
array.findIndex(elem => elem.v > 3); // 3

如果要从数组的末尾开始遍历,就必须反转数组并使用上述方法。这样做就需要一个额外的数组操作。幸运的是,Wenlu Wang 和 Daniel Rosenwasser 关于findLast() 和 findLastIndex() 的 ECMAScript 提案解决了这一问题。该提案的一个重要原因就是:语义,语义,语义,我讲三遍哈
提案地址:https://github.com/tc39/proposal-array-find-from-last

2.使用
它们的用法和find()、findIndex()基本一样,唯一不同的是它们是 从后向前 遍历数组,这两个方法适用于数组和类数组。
findLast() 会返回第一个查找到的元素,如果没有找到,就会返回 undefined;
findLastIndex() 会返回第一个查找到的元素的索引。如果没有找到,就会返回 -1;

const array = [{v: 1}, {v: 2}, {v: 3}, {v: 4}, {v: 5}];

array.findLast(elem => elem.v > 3); // {v: 5}
array.findLastIndex(elem => elem.v > 3); // 4
array.findLastIndex(elem => elem.v > 5); // undefined

3.实现(这个我看懂了)

//Array.prototype.findLast
Array.prototype.findLast = function(arr, callback, thisArg) {
  for (let index = arr.length - 1; index >= 0; index--) {
    const value = arr[index];
    if (callback.call(thisArg, value, index, arr)) {
      return value;
    }
  }
  return undefined;
}
//Array.prototype.findLastIndex
Array.prototype.findLastIndex = function(arr, callback, thisArg) {
  for (let index = arr.length - 1; index >= 0; index--) {
    const value = arr[index];
    if (callback.call(thisArg, value, index, arr)) {
      return index;
    }
  }
  return -1;
}

4. Array.fromAsync

1.概述
在 JavaScript 中内置了 Array.from 方法,它用于将类数组或者可迭代对象生成一个新的数组实例。在ECMAScript 2018中引入了异步可迭代对象。而JavaScript中一直缺少直接从异步可迭代对象生成数组的内置方法。
proposal-array-from-async 提案中提出来的 Array.fromAsync 方法就是为了解决这个问题而提出来的。
2…例子:

async function * asyncGen (n) {
  for (let i = 0; i < n; i++)
    yield i * 2;
}

// arr 将变为 [0, 2, 4, 6]`
const arr = [];
for await (const v of asyncGen(4)) {
  arr.push(v);
}

// 与上述方式是等价的
const arr = await Array.fromAsync(asyncGen(4));

Array.fromAsync 可以将异步迭代转换为 promise,并将解析为新数组。在 promise 解析之前,它将从输入值中创建一个异步迭代器,进行惰性的迭代,并将每个产生的值添加到新数组中。
与其他基于 Promise 的 API 一样,Array.fromAsync 总是会立即返回一个 promise。当 Array.fromAsync 的输入在创建其异步或同步迭代器时引发错误时,则此 promise 状态将被置为 rejected。