1. 不改变原数组设值
- 使用 with 方法,不改变原来的数组;
- 对新的数组某个索引进行设值,不改变原来的数组。
1.1 示例
let a = [2,3,4,5,6]
let c = a.with(0,9)
console.log(a)
console.log(c)
1.2 输出结果
1.3 with 方法之前的实现
- 使用…扩展语法进行数组复制
let a = [2,3,4,5,6]
let d = [...a]
d[0] = 9;
console.log(a)
console.log(d)
- 使用slice()方法进行数组复制
let a = [2,3,4,5,6]
let e = a.slice()
e[0] = 9
console.log(a)
console.log(e)
1.4 总结
- 当然复制数组的方法还有很多,这里我只举例了我最常用的两种;
- 可以看到,在不修改原数组的要求下,要改变数组的某个元素,在 with 之前,都需要先进行一步复制数组的操作,然后再对复制数组对应索引进行修改;
- 从表面看,就是将复制数组和修改索引值两步合并到一个函数,但是可以对其性能进行测试,测试一下差距。
2. Array.prototype.with 说明
Array 实例的 with() 方法是使用方括号表示法修改指定索引值的复制方法版本。它会返回一个新数组,其指定索引处的值会被新值替换。
2.1 语法
arrayInstance.with(index, value)
2.2 参数
- index 要修改的数组索引(从 0 开始),将会转换为整数。
1.1 负数索引会从数组末尾开始计数——即当 index < 0 时,会使用 index + array.length。
1.2 如果规范化后的索引超出数组边界,会抛出 RangeError。 - value 要分配给指定索引的任何值。
2.3 返回值
一个全新的数组,其中 index 索引处的元素被替换为 value。
2.4 兼容性
3. 注意
- with() 通过返回一个指定索引处的值被新值替换的新数组,来改变数组中指定索引处的值。原数组不会被修改,这使得你可以以链式调用数组方法的方式来对数组进行操作。
- 读取数组使用 at() 函数,写入数组使用 with() 函数,索引使用正数负数均可。
- with() 方法永远不会产生稀疏数组。如果原数组是稀疏的,新数组对应的空白索引位置会替换为 undefined。
- with() 方法是通用的。它只期望 this 值具有 length 属性和整数键属性。
4. 链式调用
使用 with() 方法,你可以在更新一个数组元素后继续调用其他的数组方法。
4.1 测试代码
let a = [2,3,4,5,6]
let b = a.with(2, 6).map((x) => x ** 2)
console.log(a)
console.log(b)
4.2 测试结果
5. 使用 at 和 with 函数进行读写操作
注意:当 index > array.length 或 index < -array.length 时 with 函数会抛出异常,at 读取函数直接返回 undefined。
6. 永远不会产生稀疏数组
在稀疏数组上使用 with(),with() 方法总会创建一个密集数组。
6.1 示例代码
let z = [1,45,,56,23,,34,,8]
z.with(0,10)
6.2 结果
7. 在非数组对象上调用 with
with() 方法创建并返回一个新数组。它读取 this 的 length 属性,然后访问其键是小于 length 的非负整数的每个属性。当 this 的每个属性被访问后,索引等于该属性的键的数组元素被设置为该属性的值。最后,将 index 的数组值设置为 value。
7.1 示例代码
let obj = {
length: 3,
fookey: "foo",
0: 56,
2: "rfg",
3: 9,
5: "aio"
}
Array.prototype.with.call(obj,0,2)
7.2 结果
7.3 注意
- 创建并返回一个新数组;
- 新数组的长度读取的对象的 length 属性;
- 读取的是小于 length 的非负整数的每个属性;
- 对应 index 的值就是非负整数的属性读取的值。
8. 基础性能测试
8.1 测试代码
const LARGE_ARRAY = Array(1e5).fill().map((_,i) => i)
// splice测试
console.time('splice')
const copy1 = [...LARGE_ARRAY]
copy1.splice(50000, 1, 'new')
console.timeEnd('splice')
// with()测试
console.time('with')
const copy2 = LARGE_ARRAY.with(50000, 'new')
console.timeEnd('with')
8.2 测试结果
11. 不改变原数组的数组方法
- Array.prototype.toSpliced:不修改原始数组,与 splice() 类似,可以同时完成多个操作,在数组中给定的索引开始移除指定数量的元素,然后在相同的索引处插入给定的元素。
- Array.prototype.toSorted:不修改原始数组,同时对数组进行排序,返回内容是新数组。对应的原始方法是 sort() 方法,两者参数一致。
- Array.prototype.toReversed:不修改原始数组,同时数组反序,返回值是新数组。改变原数组的反序方法是 reverse() 方法,参数都是一样的。
- Array.prototype.slice:不修改原始数组,返回一个新的数组对象,这一对象是一个由 start 和 end 决定的原数组的浅拷贝(包括 start,不包括 end),其中 start 和 end 代表了数组元素的索引。
- Array.prototype.map:不修改原始数组,创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
- Array.prototype.flat:不修改原始数组,创建一个新的数组,并根据指定深度递归地将所有子数组元素拼接到新的数组中。
- Array.prototype.filter:不修改原始数组,创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。
- Array.prototype.concat:不修改原始数组,用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
10. 总结
- Array.prototype.with() 通过复制原数组实现不可变修改,适合小数据量和函数式场景,但需注意大数组的性能损耗。
- Array.prototype.with() 是 ECMAScript 2023(ES14)新增的数组方法,其核心目的是以不可变方式修改数组的某个元素,返回新数组,而非修改原数组。