Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ECMAScript 2023:为JavaScript带来新的数组复制方法

ECMAScript 2023:为JavaScript带来新的数组复制方法

作者头像
zz_jesse
发布于 2023-11-11 12:35:41
发布于 2023-11-11 12:35:41
30900
代码可运行
举报
文章被收录于专栏:前端技术江湖前端技术江湖
运行总次数:0
代码可运行

整理 | 丁晓昀、核子可乐

ECMAScript 2023 规范最近已经定稿,其中提出的 Array 对象新方法将为 JavaScript 带来更好的可预测性和可维护性。toSorted、toReversed、toSpliced 和 with 方法允许用户在不更改数据的情况下对数据执行操作,实质是先制造副本再更改该副本。

变异与副作用

Array 对象总是有点自我分裂。sort、reverse 和 splice 等方法会就地更改数组,concat、map 和 filter 等其他方法则是先创建数组副本,再对副本执行操作。当我们通过操作让对象产生变异时,则会产生一种副作用,导致系统其他位置发生意外行为。

举例来说,当 reverse 一个数组时会发生如下情况。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const reversed = languages.reverse();
console.log(reversed);
// => [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(languages);
// => [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(Object.is(languages, reversed));
// => true

可以看到,原始数组已经反转,但即使我们将反转数组的结果分配给一个新变量,两个变量也仍指向同一数组。

变异数组和 React

数组变异方法中一个最著名的问题,就是在 React 组件中使用时的异常。我们无法变异数组,之后尝试将其设置为新状态,因为数组本身是同一个对象且不会触发新的渲染。相反,我们需要先复制该数组,然后改变副本再将其设置为新状态。因此,React 文档专门有一整页解释了如何更新状态数组。

先复制,后变异

解决这个问题的方法,是先复制数组,之后再执行变异。我们可以通过几种不同方法来生成数组副本,包括:Array.from,展开运算符,或者调用不带参数的 slice 函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const reversed = Array.from(languages).reverse();
// => [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]
console.log(languages);
// => [ 'JavaScript', 'TypeScript', 'CoffeeScript' ]
console.log(Object.is(languages, reversed));
// => false

有办法能解决当然很好,总之请千万注意不同复制操作间是有区别的。

新方法可随副本变化

此次公布的新方法正是为此而生。toSorted、toReversed、toSpliced 和 with 都能复制原始数组、变更副本再返回结果。如此一来,每项操作都更易于编写,开发者只需调用一个函数即可,代码阅读起来也更容易、不必预先考虑到底要用具体哪种数组复制方法。下面,我们来看这几种新方法的区别。

Array.prototype.toSorted

其中 toSorted 函数会返回一个新的、经过排序的数组。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const sorted = languages.toSorted();
console.log(sorted);
// => [ 'CoffeeScript', 'JavaScript', 'TypeScript' ]
console.log(languages);
// => [ 'JavaScript', 'TypeScript', 'CoffeeScript' ]

除了复制之外,sort 函数还会引发一些意想不到的行为,toSorted 也继承了这种特点。所以在对带有重音字符的数字或字符串进行排序时,大家仍然要小心。比如准备一个 comparator 比较器函数(例如 String's localeCompare)来生成当前查找的结果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const numbers = [5, 3, 10, 7, 1];
const sorted = numbers.toSorted();
console.log(sorted);
// => [ 1, 10, 3, 5, 7 ]
const sortedCorrectly = numbers.toSorted((a, b) => a - b);
console.log(sortedCorrectly);
// => [ 1, 3, 5, 7, 10 ]
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const strings = ["abc", "äbc", "def"];
const sorted = strings.toSorted();
console.log(sorted);
// => [ 'abc', 'def', 'äbc' ]
const sortedCorrectly = strings.toSorted((a, b) => a.localeCompare(b));
console.log(sortedCorrectly);
// => [ 'abc', 'äbc', 'def' ]

Array.prototype.toReversed

使用 toReversed 函数,会返回一个按相反顺序排序的新数组。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const reversed = languages.toReversed();
console.log(reversed);
// => [ 'CoffeeScript', 'TypeScript', 'JavaScript' ]

之前将 reverse 的结果分配给新变量时会出问题,因为原始数组也发生了变异。但现在,大家可以使用 toReversed 或者 toSorted 来复制数组并更改副本。

Array.prototype.toSpliced

toSpliced 函数与原始版本的 splice 略有不同。splice 是在提供的索引处删除和添加元素来更改现有数组,再返回一个包含数组中所删除元素的数组。toSpliced 则直接返回一个新数组,其中不含被删除的元素,且包含所添加的元素。其工作方式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const spliced = languages.toSpliced(2, 1, "Dart", "WebAssembly");
console.log(spliced);
// => [ 'JavaScript', 'TypeScript', 'Dart', 'WebAssembly' ]

如果我们使用 splice 作为返回值,那么 toSpliced 就不能直接作为替代使用。换言之,如果大家想在不改变原始数组的情况下知晓被删除的元素是什么,就应使用 slice 复制方法。

更麻烦的是,splice 和 slice 使用的参数也有不同。splice 使用的是一个索引加该索引之后待删除的元素数量;slice 则使用两个索引,分别对应开始和结束。如果要使用 toSpliced 代替 splice,但又想获取被删除的元素,则可对原始数组应用 toSpliced 和 slice,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const startDeletingAt = 2;
const deleteCount = 1;
const spliced = languages.toSpliced(startDeletingAt, deleteCount, "Dart", "WebAssembly");
const removed = languages.slice(startDeletingAt, startDeletingAt + deleteCount);
console.log(spliced);
// => [ 'JavaScript', 'TypeScript', 'Dart', 'WebAssembly' ]
console.log(removed);
// => [ 'CoffeeScript' ]

Array.prototype.with

with 函数所代表的复制方法,等同于使用方括号表示方来更改数组内的一个元素。因此,与其通过以下方式直接更改数组:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
languages[2] = "WebAssembly";
console.log(languages);
// => [ 'JavaScript', 'TypeScript', 'WebAssembly' ]

可以复制该数组再执行更改:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const languages = ["JavaScript", "TypeScript", "CoffeeScript"];
const updated = languages.with(2, "WebAssembly");
console.log(updated);
// => [ 'JavaScript', 'TypeScript', 'WebAssembly' ]
console.log(languages);
// => [ 'JavaScript', 'TypeScript', CoffeeScript' ]

不只是数组

此次发布的新方法不仅适用于常规的数组对象。您可以在任意 TypedArray 上使用 toSorted、toReversed 和 with 方法,包括 Int8Array 到 BigUint64Array 等各种类型。但因为 TypedArrays 没有 splice 方法,因此无法使用 toSpliced 方法。

注意事项

前文提到,map、filter 和 concat 等方法也都采取先复制再更改的思路,但这些方法与新的复制方法间仍有不同。如果对内置的 Array 对象进行扩展,并在实例上使用 map、flatMap、filter 或 concat,则会返回相同类型的新实例。但如果您扩展一个 Array 并使用 toSorted、toReversed、toSpliced 或者 with,则返回的仍是普通 Array。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyArray extends Array {}
const languages = new MyArray("JavaScript", "TypeScript", "CoffeeScript");
const upcase = languages.map(language => language.toUpperCase());
console.log(upcase instanceof MyArray);
// => true
const reversed = languages.toReversed();
console.log(reversed instanceof MyArray);
// => false

可以使用 MyArray.from 将其转回您的自定义 Array:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyArray extends Array {}
const languages = new MyArray("JavaScript", "TypeScript", "CoffeeScript");
const reversed = MyArray.from(languages.toReversed());
console.log(reversed instance of MyArray);
// => true

支持

虽然 ECMAScript 2023 的规范刚刚成形,但已经为本文提到的新数组方法提供了良好支持。Chrome 110、Safari 16.3、Node.js 20 和 Deno1.31 都支持这四种新方法,尚不支持的平台也有 polyfills 和 shims 作为过渡方案。

JavaScript 仍在不断改进

很高兴看到 ECMAScript 标准新增了这么多有意义的内容,让我们能轻松编写出可预测性更好的代码。其他一些提案也已被纳入 ES2023,感兴趣的朋友可以移步此处:https://github.com/tc39/proposals/blob/HEAD/finished-proposals.md

至于未来的规范发展方向,推荐大家参考整个 TC39 提案库:https://github.com/tc39/proposals

附录:ES2023 新特性概述

数组倒序查找

Array.prototype.findLast 和 Array.prototype.findLastIndex

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let nums = [5,4,3,2,1];
let lastEven = nums.findLast((num) => num % 2 === 0); // 2
let lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0); // 3

Hashbang 语法

#! for JS

此脚本的第一行以 #!开头,表示可在注释中包含任意文本。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#!/usr/bin/env node
// in the Script Goal
'use strict';
console.log(1);

将符号作为 WeakMap 键

在弱集合和注册表中使用符号

注意:注册的符号不可作为 weakmap 键。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let sym = Symbol("foo");
let obj = {name: "bar"};
let wm = new WeakMap();
wm.set(sym, obj);
console.log(wm.get(sym)); // {name: "bar"}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sym = Symbol("foo");
let ws = new WeakSet();
ws.add(sym);
console.log(ws.has(sym)); // true
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sym = Symbol("foo");
let wr = new WeakRef(sym);
console.log(wr.deref()); // Symbol(foo)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sym = Symbol("foo");
let cb = (value) => {
  console.log("Finalized:", value);
};
let fr = new FinalizationRegistry(cb);
obj = {name: "bar"};
fr.register(obj, "bar", sym);
fr.unregister(sym);

通过副本更改数组

返回更改后的 Array 和 TypeArray 副本。

注意:类型数组不可 tospliced。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const greek = ['gamma', 'aplha', 'beta']
greek.toSorted(); // [ 'aplha', 'beta', 'gamma' ]
greek; // [ 'gamma', 'aplha', 'beta' ]
const nums = [0, -1, 3, 2, 4]
nums.toSorted((n1, n2) => n1 - n2); // [-1,0,2,3,4]
nums; // [0, -1, 3, 2, 4]
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const greek = ['gamma', 'aplha', 'beta']
greek.toReversed(); // [ 'beta', 'aplha', 'gamma' ]
greek; // [ 'gamma', 'aplha', 'beta' ]
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const greek = ['gamma', 'aplha', 'beta']
greek..toSpliced(1,2); // [ 'gamma' ]
greek; // [ 'gamma', 'aplha', 'beta' ]
greek.toSpliced(1,2, ...['delta']); // [ 'gamma', 'delta' ]
greek; // [ 'gamma', 'aplha', 'beta' ]
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const greek = ['gamma', 'aplha', 'beta']
greek..toSpliced(1,2); // [ 'gamma' ]
greek; // [ 'gamma', 'aplha', 'beta' ]
greek.toSpliced(1,2, ...['delta']); // [ 'gamma', 'delta' ]
greek; // [ 'gamma', 'aplha', 'beta' ]
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const greek = ['gamma', 'aplha', 'beta'];
greek.with(2, 'bravo'); // [ 'gamma', 'aplha', 'bravo' ]
greek; //  ['gamma', 'aplha', 'beta'];

参考链接:https://h3manth.com/ES2023/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端技术江湖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JavaScript 数组新增 4 个非破坏性方法!
今天聊 JavaScript 的最新提案,这是我 最新技术提案 专栏的第 16 篇文章了,感谢读者们一如既往的支持!
ConardLi
2022/05/23
6670
JavaScript 数组新增 4 个非破坏性方法!
ES2023来了!深入解析JavaScript的最新更新
使用 findLast 方法从数组的末尾开始查找第一个满足条件(n => n,即所有元素)的元素。因为所有元素都满足条件,所以它返回了数组的最后一个元素 {a: 4, b: 4}。
前端小智@大迁世界
2023/06/10
4160
ECMAScript 2023 新特性解读,附代码示例
大家好,今天我们要聊聊 ECMAScript 2023 —— JavaScript 的第14版,它带来了一些精彩的更新,将让我们的编程生活变得更加轻松愉快。本文将逐一解读这些变化,并说明它们为何如此有用。那就让我们一起来看看这些如圣诞礼物般送到我们手中的新方法吧!
前端达人
2023/12/13
3660
ECMAScript 2023 新特性解读,附代码示例
在 TypeScript 中利用 ES2023 数组方法进行 React
ES2023 带来了新的数组方法,其特点是返回修改后的数组副本,而不是修改原始数组。这种小改变可以极大地影响状态管理的安全性,特别是在像 React 这样的框架中。
zayyo
2023/11/29
3160
热点面试题: Array中有哪些非破坏性方法?
•问题标注 Q:(question)•答案标注 R:(result)•注意事项标准:A:(attention matters)•详情描述标注:D:(detail info)•总结标注:S:(summary)•分析标注:Ana:(analysis)•提示标注:T:(tips)
沉浸式趣谈
2024/03/13
720
热点面试题: Array中有哪些非破坏性方法?
【完整汇总】近 5 年 JavaScript 新特性完整总览
本文深度解析近 5 年来 JavaScript 的所有重要更新,帮助你快速了解 JavaScript 新特性。
沉浸式趣谈
2025/03/04
1050
【完整汇总】近 5 年 JavaScript 新特性完整总览
分享 7 个鲜为人知的JS数组方法
JavaScript 数组除了 map()、filter()、find() 和 push() 之外还有更多功能。今天这篇文章就来给大家分享一些鲜有人知道的数组方法,我们现在开始吧。
前端达人
2024/03/29
2250
分享 7 个鲜为人知的JS数组方法
《现代Javascript高级教程》JavaScript数组
在JavaScript中,数组(Array)是一种重要且广泛应用的数据结构,用于存储和操作一组有序的数据。JavaScript提供了丰富的数组方法和属性,使我们能够方便地对数组进行增删改查等操作。本文将详细介绍JavaScript数组的方法API、属性,并探讨如何模拟实现数组的API。此外,还将介绍数组的应用场景,帮助读者更好地理解和应用数组。
linwu
2023/07/31
2210
2024年2月前端资讯动态:JSR新仓库革新及Set方法等全新特性
2024年2月前端技术领域再次迎来了一系列激动人心的更新和进展。无论是新兴的包仓库JSR,还是JavaScript提案中的Set方法、Array.prototype.with()的加入,都预示着前端开发的未来将更加灵活和强大。本文将为您详细介绍这些技术的最新动态,帮助您紧跟前端开发的最前沿。
前端达人
2024/02/21
2820
2024年2月前端资讯动态:JSR新仓库革新及Set方法等全新特性
Ecmascript语法之Symbol
Symbol 概述 作为属性名的Symbol 实例:消除魔术字符串 属性名的遍历 Symbol.for(),Symbol.keyFor() 实例:模块的 Singleton 模式 内置的Symbol值 概述 ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。 E
xiangzhihong
2018/02/06
1.3K0
ECMAScript 6 特性ECMAScript 6 特性
ECMAScript 6,也被称做ECMAScript 2015,是ECMAScript标准的下一个版本。这个标准预计将于2015年6月被正式批准。ES6是这门语言的一次重大更新,自ES5以来,该语言的首次更新是在2009年。主流Javascript引擎对ES6相关特性的实现也正在进行中。
用户2936342
2018/08/27
6290
分享 20 个 Javascript 中的数组方法,收藏
forEach()按索引升序为数组中的每个元素调用一次提供的callbackFn函数。对于已删除或未初始化的索引属性,不会调用它。
艾编程
2022/12/28
1.4K0
JavaScript 数据结构与算法(二)数组结构
几乎所有的编程语言都原生支持数组类型,因为数组是最简单的内存数据结构。数组通常情况下用于存储一系列同一种数据类型的值。但在 JavaScript 里,数组中可以保存不同类型的值。但我们还是要遵守最佳实践,别这么做(大多数语言都没这个能力)。
XPoet
2021/04/26
4020
如何使用 JavaScript 将数组拆分为偶数块
数组是JavaScript编程中最常用的结构之一,这也是为什么了解它的内置方法很重要。
前端小智@大迁世界
2022/06/15
2.9K0
ECMAScript13 中11个令人惊叹的 JavaScript 新特性
与许多其他编程语言一样,JavaScript 也在不断发展。每年,该语言都会通过新功能变得更加强大,使开发人员能够编写更具表现力和简洁的代码。 小编今天就为大家介绍ES13中添加的最新功能,并查看其用法示例以更好地理解它们。
葡萄城控件
2023/10/16
2730
软件开发入门教程网站之TypeScript Array(数组)
如果数组声明时未设置类型,则会被认为是 any 类型,在初始化时根据第一个元素的类型来推断数组的类型。
iOS程序应用
2023/01/11
3770
JavaScript数组的常用方法
JavaScript数组是一种常见的数据类型,它由多个元素组成。以下是一些常用的JavaScript数组方法:
心安事随
2024/07/29
1190
JavaScript编码之路 【JavaScript之操作数组、字符串方法汇总】
数组基本操作可以归纳为增、删、改、查,需要留意的是哪些方法会对原数组产生影响,哪些方法不会
HelloWorldZ
2024/03/20
2470
JavaScript编码之路 【JavaScript之操作数组、字符串方法汇总】
JavaScript中splice方法的使用「建议收藏」
在JavaScript中,arrObject.splice()方法是处理数组的利器,利用它可以实现在指定位置删除、替换、插入指定数量的元素。 其语法为: arrayObject.splice(index,howmany,item1,…,itemX) 含义为从index开始,删除howmanry个元素,并在原地插入item1, …, itemN,最后返回被删除的数组。
全栈程序员站长
2022/10/05
1.8K0
javascript对数组的基本操作
创建数组有两种方法,一个是通过new方法来创建,另一个就是直接通过字面量来创建,看网上有说通过new关键字来创建数组对象要比直接通过字面量来创建数组耗内存,这个我没有实际测试过,个人感觉在小数据量的时候两者之间的差距是相同的。我个人比较倾向于使用字面量来创建,方便简洁。
OECOM
2020/07/02
4080
推荐阅读
相关推荐
JavaScript 数组新增 4 个非破坏性方法!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验