ECMAScript 2023將新增的9個數(shù)組方法
大家好,我是 CUGGZ。
ECMAScript 規(guī)范每年都會更新一次,正式標(biāo)準(zhǔn)化 JavaScript 語言的 ECMAScript 的下一次年度更新將在 2023 年 6 月左右獲得批準(zhǔn),這將是 ECMAScript 的第 14 版。所有在 2023 年 3 月之前達(dá)到階段 4 的提案都將包含在 ECMAScript 2023 標(biāo)準(zhǔn)中。 對于一個提案,從提出到最后被納入 ECMAScript 標(biāo)準(zhǔn),總共分為五步:
stage0(strawman):任何TC39的成員都可以提交。
stage1(proposal):進(jìn)入此階段就意味著這一提案被認(rèn)為是正式的了,需要對此提案的場景與API進(jìn)行詳盡的描述。
stage2(draft):演進(jìn)到這一階段的提案如果能最終進(jìn)入到標(biāo)準(zhǔn),那么在之后的階段都不會有太大的變化,因為理論上只接受增量修改。
state3(candidate):這一階段的提案只有在遇到了重大問題才會修改,規(guī)范文檔需要被全面的完成。
state4(finished):這一階段的提案將會被納入到ES每年發(fā)布的規(guī)范之中。
根據(jù) Erick Wendel(微軟 MVP、谷歌開發(fā)專家、@nodejs合作者)的預(yù)測,ECMAScript 2023 可能會新增以下數(shù)組方法(3??、4??為所處提案階段):
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. 通過副本更改數(shù)組
通過副本更改數(shù)組的提案目前處于第 3 階段。該提案為數(shù)組和類型化數(shù)組提出了四種新的方法:
Array.prototype.toReversed()
Array.prototype.toSorted()
Array.prototype.toSpliced()
Array.prototype.with()
提案地址:https://github.com/tc39/proposal-change-array-by-copy
為什么會有這個提案呢?我們知道,大多數(shù)的數(shù)組方法都是非破壞性的,也就是說,在數(shù)組執(zhí)行該方法時,不會改變原數(shù)組,比如 filter() 方法:
const arr = ['a', 'b', 'b', 'a'];
const result = arr.filter(x => x !== 'b');
console.log(result); // ['a', 'a']
當(dāng)然,也有一些是破壞性的方法,它們在執(zhí)行時會改變原數(shù)組,比如 sort() 方法:
const arr = ['c', 'a', 'b'];
const result = arr.sort();
console.log(result); // ['a', 'b', 'c']
在數(shù)組的方法中,下面的方法是具有破壞性的:
reverse()
sort()
splice()
如果我們想要這些數(shù)組方法應(yīng)用于數(shù)組而不改變它,可以使用下面任意一種形式:
const sorted1 = arr.slice().sort();
const sorted2 = [...arr].sort();
const sorted3 = Array.from(arr).sort();
可以看到,我們首先需要創(chuàng)建數(shù)組的副本,再對這個副本進(jìn)行修改。
因此改提案就引入了這三個方法的非破壞性版本,因此不需要手動創(chuàng)建副本再進(jìn)行操作:
reverse() 的非破壞性版本:toReversed()
sort() 非破壞性版本:toSorted(compareFn)
splice() 非破壞性版本:toSpliced(start, deleteCount, ...items)
該提案將這些函數(shù)屬性引入到 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 處的數(shù)組元素,即 arr[index]=value 的非破壞性版本。
所有這些方法都將保持目標(biāo)數(shù)組不變,并返回它的副本并執(zhí)行更改。這些方法適用于數(shù)組,也適用于類型化數(shù)組,即以下類的實例:
Int8Array
Uint8Array
Uint8ClampedArray
Int16Array
Uint16Array
Int32Array
Uint32Array
Float32Array
Float64Array
BigInt64Array
BigUint64Array
TypedArray是一種通用的固定長度緩沖區(qū)類型,允許讀取緩沖區(qū)中的二進(jìn)制數(shù)據(jù)。其在WEBGL規(guī)范中被引入用于解決Javascript處理二進(jìn)制數(shù)據(jù)的問題。類型化數(shù)組也是數(shù)組,只不過其元素被設(shè)置為特定類型的值。
類型化數(shù)組的核心就是一個名為 ArrayBuffer 的類型。每個ArrayBuffer對象表示的只是內(nèi)存中指定的字節(jié)數(shù),但不會指定這些字節(jié)用于保存什么類型的數(shù)據(jù)。通過ArrayBuffer能做的就是為了將來使用而分配一定數(shù)量的字節(jié)。
這些提案也適用于元組,元組相當(dāng)于不可變的數(shù)組。它們擁有數(shù)組的所有方法——除了破壞性的方法。因此,將后者的非破壞性版本添加到數(shù)組對元組是有幫助的,這意味著我們可以使用相同的方法來非破壞性地更改數(shù)組和元組。
(1)Array.prototype.toReversed()
toReversed() 是 reverse() 方法的非破壞性版本:
const arr = ['a', 'b', 'c'];
const result = arr.toReversed();
console.log(result); // ['c', 'b', 'a']
console.log(arr); // ['a', 'b', 'c']
下面是 toReversed() 方法的一個簡單的 polyfill:
if (!Array.prototype.toReversed) {
Array.prototype.toReversed = function () {
return this.slice().reverse();
};
}
(2)Array.prototype.toSorted()
toSorted() 是 sort() 方法的非破壞性版本:
const arr = ['c', 'a', 'b'];
const result = arr.toSorted();
console.log(result); // ['a', 'b', 'c']
console.log(arr); // ['c', 'a', 'b']
下面是 toSorted() 方法的一個簡單的 polyfill:
if (!Array.prototype.toSorted) {
Array.prototype.toSorted = function (compareFn) {
return this.slice().sort(compareFn);
};
}
(3)Array.prototype.toSpliced()
splice() 方法比其他幾種方法都復(fù)雜,其使用形式:splice(start, deleteCount, ...items)。該方法會從從 start 索引處開始刪除 deleteCount個元素,然后在 start 索引處開始插入item 中的元素,最后返回已經(jīng)刪除的元素。
toSpliced 是 splice() 方法的非破壞性版本,它會返回更新后的數(shù)組,原數(shù)組不會變化,并且我們無法再得到已經(jīng)刪除的元素:
const arr = ['a', 'b', 'c', 'd'];
const result = arr.toSpliced(1, 2, 'X');
console.log(result); // ['a', 'X', 'd']
console.log(arr); // ['a', 'b', 'c', 'd']
下面是 toSpliced() 方法的一個簡單的 polyfill:
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'];
const result = arr.with(1, 'X');
console.log(result); // ['a', 'X', 'c']
console.log(arr); // ['a', 'b', 'c']
下面是 with() 方法的一個簡單的 polyfill:
if (!Array.prototype.with) {
Array.prototype.with = function (index, value) {
const copy = this.slice();
copy[index] = value;
return copy;
};
}
2. 數(shù)組分組
(1)概述
在日常開發(fā)中,數(shù)組分組是一種極其常見的操作。因此,proposal-array-grouping 提案就提出了兩個新的數(shù)組方法:
array.group(callback, thisArg?)
array.groupToMap(callback, thisArg?)
提案地址:https://github.com/tc39/proposal-array-grouping
下面是這兩個方法的類型簽名:
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>>
這兩個方法都用來對數(shù)組進(jìn)行分組:
輸入:一個數(shù)組;
輸出:組,每個組都有一個組key,以及一個包含組成員的數(shù)組。
這兩個方法都會對數(shù)組進(jìn)行遍歷,它們會向其回調(diào)請求組鍵并將元素添加到相應(yīng)的組中。這兩個方法在表示組的方式上有所不同:
group():將組存儲在對象中:組鍵存儲為屬性鍵,組成員存儲為屬性值;
groupToMap():將組存儲在 Map 中:組鍵存儲為 Map 鍵,組成員存儲為 Map 值。
那這兩個方法該如何選擇呢?我們知道,JavaScript 中對象是支持解構(gòu)的,如果想要使用解構(gòu)來獲取數(shù)組中的值,比如,對于上面對象,可以通過解構(gòu)獲取三個不同組的值:
const {vegetables, fruit, meat} = result;
而 Map 的好處就是它的 key 不限于字符串和symbol,更加自由。
(2)使用
下面來看幾個實用例子。假如執(zhí)行 Promise.allSettled() 方法返回的數(shù)組如下:
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 結(jié)果如下:
[
{ status: 'fulfilled', value: 'Jane' },
{ status: 'fulfilled', value: 'John' },
]
// rejected 結(jié)果如下:
[
{ status: 'rejected', reason: 'Jhon' },
{ status: 'rejected', reason: 'Jaen' },
{ status: 'rejected', reason: 'Jnoh' },
]
在這個例子中,使用 group() 的效果會更好,因為可以使用解構(gòu)輕松獲取需要組的值。
假如想要對以下數(shù)組中人根據(jù)國家進(jìn)行分組:
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 的執(zhí)行結(jié)果和以下 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)polyfill
下面來實現(xiàn)一下這兩個方法:
Array.prototype.group
Array.prototype.group = function (callback, thisArg) {
const result = Object.create(null);
for (const [index, elem] of this.entries()) {
const groupKey = callback.call(thisArg, elem, index, this);
if (! (groupKey in result)) {
result[groupKey] = [];
}
result[groupKey].push(elem);
}
return result;
};
Array.prototype.groupToMap
Array.prototype.groupToMap = function (callback, thisArg) {
const result = new Map();
for (const [index, elem] of this.entries()) {
const groupKey = callback.call(thisArg, elem, index, this);
let group = result.get(groupKey);
if (group === undefined) {
group = [];
result.set(groupKey, group);
}
group.push(elem);
}
return result;
};
3. 從尾到頭搜索數(shù)組
(1)概述
在 JavaScript 中,通過 find() 和 findIndex() 查找數(shù)組中的值是一種常見做法。不過,這些方法從數(shù)組的開始進(jìn)行遍歷:
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
如果要從數(shù)組的末尾開始遍歷,就必須反轉(zhuǎn)數(shù)組并使用上述方法。這樣做就需要一個額外的數(shù)組操作。幸運(yùn)的是,Wenlu Wang 和 Daniel Rosenwasser 關(guān)于findLast() 和 findLastIndex() 的 ECMAScript 提案解決了這一問題。該提案的一個重要原因就是:語義。
提案地址:https://github.com/tc39/proposal-array-find-from-last
(2)使用
它們的用法和find()、findIndex()類似,唯一不同的是它們是 從后向前 遍歷數(shù)組,這兩個方法適用于數(shù)組和類數(shù)組。
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)polyfill
下面來實現(xiàn)一下這兩個方法:
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)參考源碼
lodash 中也提供了類似方法,下面是相關(guān)源碼:
findLast()
import findLastIndex from './findLastIndex.js'
import isArrayLike from './isArrayLike.js'
/**
* This method is like `find` except that it iterates over elements of
* `collection` from right to left.
*
* @since 2.0.0
* @category Collection
* @param {Array|Object} collection The collection to inspect.
* @param {Function} predicate The function invoked per iteration.
* @param {number} [fromIndex=collection.length-1] The index to search from.
* @returns {*} Returns the matched element, else `undefined`.
* @see find, findIndex, findKey, findLastIndex, findLastKey
* @example
*
* findLast([1, 2, 3, 4], n => n % 2 == 1)
* // => 3
*/
function findLast(collection, predicate, fromIndex) {
let iteratee
const iterable = Object(collection)
if (!isArrayLike(collection)) {
collection = Object.keys(collection)
iteratee = predicate
predicate = (key) => iteratee(iterable[key], key, iterable)
}
const index = findLastIndex(collection, predicate, fromIndex)
return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined
}
export default findLast
findLastIndex()
import baseFindIndex from './.internal/baseFindIndex.js'
import toInteger from './toInteger.js'
/**
* This method is like `findIndex` except that it iterates over elements
* of `collection` from right to left.
*
* @since 2.0.0
* @category Array
* @param {Array} array The array to inspect.
* @param {Function} predicate The function invoked per iteration.
* @param {number} [fromIndex=array.length-1] The index to search from.
* @returns {number} Returns the index of the found element, else `-1`.
* @see find, findIndex, findKey, findLast, findLastKey
* @example
*
* const users = [
* { 'user': 'barney', 'active': true },
* { 'user': 'fred', 'active': false },
* { 'user': 'pebbles', 'active': false }
* ]
*
* findLastIndex(users, ({ user }) => user == 'pebbles')
* // => 2
*/
function findLastIndex(array, predicate, fromIndex) {
const length = array == null ? 0 : array.length
if (!length) {
return -1
}
let index = length - 1
if (fromIndex !== undefined) {
index = toInteger(fromIndex)
index = fromIndex < 0
? Math.max(length + index, 0)
: Math.min(index, length - 1)
}
return baseFindIndex(array, predicate, index, true)
}
export default findLastIndex
4. Array.fromAsync
在 JavaScript 中內(nèi)置了 Array.from 方法,它用于將類數(shù)組或者可迭代對象生成一個新的數(shù)組實例。在ECMAScript 2018中引入了異步可迭代對象。而JavaScript中一直缺少直接從異步可迭代對象生成數(shù)組的內(nèi)置方法。
proposal-array-from-async 提案中提出來的 Array.fromAsync 方法就是為了解決這個問題而提出來的。
下面來看一個簡單的例子:
async function * asyncGen (n) {
for (let i = 0; i < n; i++)
yield i * 2;
}
// arr 將變?yōu)?[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 可以將異步迭代轉(zhuǎn)換為 promise,并將解析為新數(shù)組。在 promise 解析之前,它將從輸入值中創(chuàng)建一個異步迭代器,進(jìn)行惰性的迭代,并將每個產(chǎn)生的值添加到新數(shù)組中。
與其他基于 Promise 的 API 一樣,Array.fromAsync 總是會立即返回一個 promise。當(dāng) Array.fromAsync 的輸入在創(chuàng)建其異步或同步迭代器時引發(fā)錯誤時,則此 promise 狀態(tài)將被置為 rejected。
提案地址:https://github.com/tc39/proposal-array-from-async
作者:CUGGZ
歡迎關(guān)注微信公眾號 :前端充電寶