ECMAScript 2023: 探索前沿的JavaScript新特性

452 阅读8分钟

在不断进步的数字世界中,JavaScript 作为一种核心技术,始终处于不断发展和进化的状态。每一次的更新都不仅仅是对语言的改进,更是对我们如何构建和理解网页与应用的全新认识。

在本文中,我们将深入探讨 ECMAScript 2023 的关键更新,分析它们如何影响日常编程实践,并提供实际示例以展示这些新功能的应用。无论你是前端开发新手还是经验丰富的全栈工程师,了解这些最新的特性都将对你的编程旅程产生深远的影响。

1. 允许 Symbol 作为 WeakMap 键

在JavaScript中,使用对象作为数据存储的键有时会导致意外的副作用。对象是引用类型,将它们用作键可能会无意中导致内存泄漏或出现意外行为。Symbol 提供了解决此问题的方法,因为它们是唯一且不可变的,确保键是独特且安全免于冲突。

想象我们正在开发一个管理对象私有数据的库。我们希望使用 WeakMap 创建一个私有存储机制,但避免与对象键产生潜在冲突。这时 Symbol 就派上了用场。

// 旧的方式
const privateData = new WeakMap();
const obj = {};

privateData.set(obj, '私有数据');
console.log(privateData.get(obj)); // 输出:"私有数据"
// 如果外部有对 obj 的引用,那么这个数据不会被垃圾回收机制回收
// ==================================================

// 新的方式:使用 WeakMap 和 symbol
const privateData = new WeakMap();
const obj = {}
const key = Symbol(); // 创建一个唯一的 Symbol 作为 key

privateData.set(key, '私有数据'); // 使用 Symbol key 将私有数据关联起来
console.log(privateData.get(key)); // 输出:"私有数据"
// Symbol 作为键不会影响 obj 的垃圾回收

我们正在使用一个以 Symbol 为键的 WeakMap。Symbol 是唯一且不可变的,使得它非常适合用作键。WeakMap 对键持有弱引用,这意味着如果作为键的对象在其他地方没有被引用,它可以被垃圾回收。这样可以防止内存泄漏和意外保留对象在内存中。

2. 支持 Hashbang 语法

Hashbang(也称为 Shebang)是一种在脚本文件中使用的注释语法,用于指定解释器路径。

Hashbang 由两个字符组成:#!,后面紧跟着解释器的路径。在 Unix-like 系统中,这种语法被用来指定执行脚本时使用的解释器。

在 ECMAScript 2023 中引入的 Hashbang 语法允许在 JavaScript 文件的第一行使用这种语法。例如:

#!/usr/bin/env node
// JavaScript 代码

这告诉系统使用 Node.js 来执行文件。

  • #! - 这是 Hashbang 的标记,它告诉系统这个脚本文件应当用哪个解释器来执行。

  • /usr/bin/env - 这是一个环境查找程序,用于在系统的 PATH 环境变量中查找指定的程序。在这个上下文中,它用于查找 node 程序的位置。

  • node - 这是要查找的程序的名称,在这种情况下,它是 Node.js 的命令行工具。

示例

有一个 js 文件 hello.js,代码如下:

#!/usr/bin/env node
console.log('Hello World')

执行脚本:

# 以前
node hello.js

# Hashbang: 直接通过文件路径执行脚本
hello.js

注意事项

  • Hashbang 必须严格的在文件开头,否则就会出现语法错误
  • 对于 Unix-like 系统,必须确保文件具有执行权限(使用 chmod +x 文件名

3. Array.prototype.findLast()

从数组的末尾开始查找,直到找到满足给定测试函数的第一个元素。这个方法的工作方式与现有的 Array.prototype.find() 相似,但区别在于搜索的方向是从数组的末尾向前进行。

使用 findLast() 方法时,你需要提供一个测试函数,该函数会被数组中的每个元素(从后向前)调用。一旦找到第一个满足测试函数条件的元素,findLast() 就会返回这个元素。如果没有元素满足条件,它将返回 undefined

语法

arr.findLast(callbackFn(element, index, array), thisArg)

参数

  • callbackFn:在数组的每个元素上执行的测试函数,接收三个参数:
    • element:当前正在处理的元素
    • index(可选):当前正在处理的元素的索引
    • array(可选):调用 findLast 的数组
  • thisArg(可选):执行 callbackFn 时,用作 this 的值

返回值

  • 返回数组中满足提供的测试函数索引最高的元素
  • 如果没有元素满足测试函数,返回 undefined

示例

const inventory = [
  { name: "apples", quantity: 2 },
  { name: "bananas", quantity: 0 },
  { name: "fish", quantity: 1 },
  { name: "cherries", quantity: 5 },
];

// 库存低时返回 true
function isNotEnough(item) {
  return item.quantity < 2;
}

console.log(inventory.findLast(isNotEnough));
// { name: "fish", quantity: 1 }

4. Array.prototype.findLastIndex()

用于从数组的末尾开始查找,找到第一个满足提供的测试函数的元素的索引。这个方法与 Array.prototype.findIndex() 相似,但是它从数组的末尾开始搜索而不是开头。

语法

arr.findLastIndexFn(callback(element, index, array), thisArg)

参数

  • callbackFn:在数组的每个元素上执行的测试函数,接收三个参数:
    • element:当前正在处理的元素
    • index(可选):当前正在处理的元素的索引
    • array(可选):调用 findLastIndex 的数组
  • thisArg(可选):执行 callbackFn 时,用作 this 的值

返回值

  • 返回数组中满足测试函数的最后一个元素的索引。
  • 如果没有找到任何匹配的元素,则返回 -1

示例

const array = [5, 12, 18, 130, 44];
const lastIndex = array.findLastIndex(element => element > 13);
console.log(lastIndex); // 输出 4

5. Array.prototype.toReversed()

它是 Array.prototype.reverse() 方法的复制版本,它返回一个元素顺序相反的新数组。

  1. Array.prototype.reverse()
  • 直接修改调用它的数组。
  • 调用后,原数组的元素顺序被反转。
  • 返回反转后的原数组。
  1. Array.prototype.toReversed()
  • 不修改原数组。
  • 创建一个新的数组,其中的元素是原数组的逆序副本。
  • 返回这个新的、元素顺序反转的数组。

toReversed 方法的引入主要是为了解决 reverse() 方法修改原数组这一问题,它符合不改变原始数据(不可变性)的编程原则。这种不可变性在许多现代编程范式中非常重要,特别是在函数式编程中,它有助于减少错误和不确定性,使得代码更容易理解和维护。

语法

arr.toReversed()

参数

  • 没有参数

返回值

  • 返回一个新数组,其中的元素按照原数组的逆序排列。

示例

const items = [1, 2, 3];
const reversedItems = items.toReversed();
console.log(reversedItems); // [3, 2, 1]
console.log(items); // [1, 2, 3]

6. Array.prototype.toSorted()

它是 Array.prototype.sort() 方法的复制版本,它返回一个新数组,其元素按升序排列。

  1. Array.prototype.sort()
  • 直接修改调用它的数组。
  • 调用后,原数组的元素顺序被更改为排序后的顺序。
  1. Array.prototype.toSorted()
  • 不修改原数组。
  • 创建一个新的数组,其中的元素是原数组排序后的副本。
  • 返回这个新的、排序后的数组。

toSorted 方法的引入主要是为了提供一种不改变原数组的排序方式。

语法

// 不传入函数
arr.toSorted()

// 传入箭头函数
arr.toSorted((a, b) => { /* … */ })

// 传入比较函数
arr.toSorted(compareFn)

// 內联比较函数
arr.toSorted(function compareFn(a, b) { /* … */ })

参数

  • compareFn: 一个定义排序顺序的函数。如果省略,则将数组元素转换为字符串,然后根据每个字符的 Unicode 码位值进行排序。
    • a: 用于比较的第一个元素
    • b: 用于比较的第二个元素

返回值

  • 返回一个新数组,其元素按升序排序

示例

const months = ["Mar", "Jan", "Feb", "Dec"];
const sortedMonths = months.toSorted();
console.log(sortedMonths); // ['Dec', 'Feb', 'Jan', 'Mar']
console.log(months); // ['Mar', 'Jan', 'Feb', 'Dec']

const values = [1, 10, 21, 2];
const sortedValues = values.toSorted((a, b) => a - b));
console.log(sortedValues); // [1, 2, 10, 21]
console.log(values); // [1, 10, 21, 2]

7. Array.prototype.toSpliced

它是 Array.prototype.splice() 方法的复制版本,它返回一个新数组,并在给定的索引处删除和/或替换了一些元素。

  1. Array.prototype.splice():
  • 直接修改调用它的数组。
  • 用于删除、添加或替换数组中的元素。
  • 返回被删除的元素组成的数组。
  1. Array.prototype.toSpliced():
  • 不修改原数组。
  • 同样用于删除、添加或替换元素,但是返回一个新的数组,包含了修改后的结果。
  • 原数组保持不变。

toSpliced 方法的引入主要是为了提供一种不改变原数组的方式来进行元素的删除、添加或替换。

语法

arr.toSpliced(start)
arr.toSpliced(start, deleteCount)
arr.toSpliced(start, deleteCount, item1)
arr.toSpliced(start, deleteCount, item1, item2, itemN)

参数

  • start:开始修改的零基索引。如果为负数,则从数组末尾开始计数。
  • deleteCount(可选):要从 start 位置删除的元素数量。如果省略或值大于 start 后的元素数量,则删除从 start 开始到数组末尾的所有元素。
  • item1, ..., itemN(可选):要添加到数组的元素

返回值

  • 返回一个新的数组,包含在 start 位置删除了 deleteCount 个元素并添加了新元素后的结果。

示例

const months = ["Jan", "Mar", "Apr", "May"];

// 添加元素
console.log(months.toSpliced(1, 0, "Feb")); // ["Jan", "Feb", "Mar", "Apr", "May"]

// 删除元素
console.log(months.toSpliced(2, 2)); // ["Jan", "Feb", "May"]

// 替换元素
console.log(months.toSpliced(1, 1, "Feb", "Mar")); // ["Jan", "Feb", "Mar", "May"]

8. Array.prototype.with()

允许根据索引修改单个数组元素,并返回一个新的数组。

语法

arr.with(index, value)

参数

  • index:要修改的元素的索引,如果为负数,则从数组末尾开始计数。
  • value:该索引位置上新的值。

返回值

  • 返回一个新的数组,其中在指定索引处的元素被替换为提供的新值。

示例

const arr = ["这", "是", "一个", "杯子"];
const newArr = arr.with(3, "瓶子");
console.log(newArr); // 输出 ["这", "是", "一个", "瓶子"]
console.log(arr); // 输出 ["这", "是", "一个", "杯子"]

ESMAScript2023 浏览器兼容性

浏览器/平台版本号版本发布时间
Chrome1102023年2月
Edge1102023年2月
Firefox1152023年7月
Opera962022年12月
Safari162022年9月
Chrome Android1102023年2月
Firefox Android1152023年7月
Opera Android742023年3月
Safari on iOS162022年9月
Webview Android1102023年2月
Node.js20.0.02023年4月