ECMAScript 規范每年都會更新一次,ECMAScript 2023 預計將于 6 月左右獲得批準,這將是 ECMAScript 的第 14 版。下面是 ECMAScript 提案中已完成并預計在 ECMAScript 2023 發布的功能!

已完成的提案:https://github.com/tc39/proposals/blob/main/finished-proposals.md
下面就來看看這些功能都有什么用!
從尾到頭搜索數組
(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
如果要從數組的末尾開始遍歷,就必須反轉數組并使用上述方法。這樣做就需要一個額外的數組操作。findLast()? 和 findLastIndex() 的就解決了這一問題。提出這兩個方法的一個重要原因就是:語義。
(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)polyfill
下面來實現一下這兩個方法:
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 中也提供了類似方法,下面是相關源碼:
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
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
Hashbang Grammar
Unix 的命令行腳本都支持#!命令,又稱為 Hashbang。這個命令放在腳本的第一行,用來指定腳本的執行器。Hashbang Grammar 功能就是想為 JavaScript 腳本引入了#!命令,這個命令寫在腳本文件或者模塊文件的第一行:
// 寫在腳本文件的第一行
#!/usr/bin/env node
'use strict';
console.log(1);
// 寫在模塊文件的第一行
#!/usr/bin/env node
export {};
console.log(1);
這樣,Unix 命令行就可以直接執行腳本了:
# 以前執行腳本
node hello.js
# 有了 hashbang 之后執行腳本
./hello.js
不過這樣的話,hashbang 就必須嚴格的在文件頭,否則就會出現語法錯誤,導致這個 JavaScript 腳本文件無法使用。
通過副本更改數組
通過副本更改數組的方法有四個:
- Array.prototype.toReversed()
- Array.prototype.toSorted()
- Array.prototype.toSpliced()
- 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 的非破壞性版本。
所有這些方法都將保持目標數組不變,并返回它的副本并執行更改。這些方法適用于數組,也適用于類型化數組,即以下類的實例:
- Int8Array
- Uint8Array
- Uint8ClampedArray
- Int16Array
- Uint16Array
- Int32Array
- Uint32Array
- Float32Array
- Float64Array
- BigInt64Array
- BigUint64Array
TypedArray是一種通用的固定長度緩沖區類型,允許讀取緩沖區中的二進制數據。其在WEBGL規范中被引入用于解決Javascript處理二進制數據的問題。類型化數組也是數組,只不過其元素被設置為特定類型的值。
類型化數組的核心就是一個名為 ArrayBuffer 的類型。每個ArrayBuffer對象表示的只是內存中指定的字節數,但不會指定這些字節用于保存什么類型的數據。通過ArrayBuffer能做的就是為了將來使用而分配一定數量的字節。
這些方法也適用于元組,元組相當于不可變的數組。它們擁有數組的所有方法——除了破壞性的方法。因此,將后者的非破壞性版本添加到數組對元組是有幫助的,這意味著我們可以使用相同的方法來非破壞性地更改數組和元組。
(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()? 方法比其他幾種方法都復雜,其使用形式:splice(start, deleteCount, ...items)?。該方法會從從 start? 索引處開始刪除 deleteCount?個元素,然后在 start? 索引處開始插入item 中的元素,最后返回已經刪除的元素。
.toSpliced? 是 .splice() 方法的非破壞性版本,它會返回更新后的數組,原數組不會變化,并且我們無法再得到已經刪除的元素:
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;
};
}
Symbol 作為 WeakMap 的鍵
目前,WeakMaps 僅允許使用對象作為鍵,這是 WeakMaps 的一個限制。新功能擴展了 WeakMap API,允許使用唯一的 Symbol 作為鍵。
這樣更易于創建和共享 key:
const weak = new WeakMap();
// 更具象征意義的key
const key = Symbol('my ref');
const someObject = { /* data data data */ };
weak.set(key, someObject);
除此之外,該功能還解決了記錄和元組提案中引入的問題:如何在原始數據類型中引用和訪問非原始值?Records & Tuples 不能包含對象、函數或方法,當這樣做時會拋出 TypeError:
const server = #{
port: 8080,
handler: function (req) { /* ... */ }, // TypeError!
};
這種限制存在是因為記錄和元組提案的關鍵目標之一是默認具有深度不可變性保證和結構相等性。接受 Symbol 值作為 WeakMap 鍵將允許 JavaScript 庫實現它們自己的類似 RefCollection 的東西,它可以重用同時不會隨著時間的推移泄漏內存:
class RefBookkeeper {
#references = new WeakMap();
ref(obj) {
const sym = Symbol();
this.#references.set(sym, obj);
return sym;
}
deref(sym) { return this.#references.get(sym); }
}
globalThis.refs = new RefBookkeeper();
const server = #{
port: 8080,
handler: refs.ref(function handler(req) { /* ... */ }),
};
refs.deref(server.handler)({ /* ... */ });