深入一点-为什么说splice-效率低呢

7次阅读

共计 2048 个字符,预计需要花费 6 分钟才能阅读完成。

原文: https://zswfx.com/articles/5da713302ddd022595ff506a

我们在使用 Array.prototype.splice 方法的时候,都会提及说它速度慢,效率低。尤其在例如 Vue或者React 框架中也不推荐使用,原因是为什么呢?

splice 方法

方法介绍如下:

方法也比较明了,就是在数组内删除或者添加元素。
如下示例:

// 添加一个元素
const arr = [1, 2, 3]
arr.splice(1, 0, 2, 3)
// [1, 2, 3, 2, 3]
// 删除元素
arr.splice(2, 2)
// arr: [1, 2, 3]

返回值则是删除元素的数组,若是添加就是空数组.

w3c 执行过程

在 w3c 中关于 splice 是如何描述过程的呢?

Array.prototype.splice 位于 ecmascript 规范中 15.4 数组章节下面的 15.4.4.12 点击这了即可

下面看关于 splice描述:

下面就用删除和添加两个例子来说明规范的操作过程:

在规范里面共有 17 步的数据操作:

  • 第 2 步中,splice 引入了新组数 A,用来存返回数据结果
  • 第 6,7 步中,得到真实开始位置与删除个数,在这里进行边界判断
  • 第 9 步这里判断当前是否是删除,若 actualDeleteCount > 0即为删除,然后得到 A 的删除数组,这里就获取到了要删除的元素,若删除元素个数为 0,则跳过 A 为空数组,k 为 0, 否则 k 为删除个数,A 为删除元素集合. actualStart 为传入的数组中某个下标值,actualDeleteCount 为传入某个个数范围是 0-len
  • 在 12 步判断添加元素是否多与删除个数,若少于删除个数,数组长度减少。
  • 第 13 步判断添加元素多余删除元素个数,数组长度增加。见下图示
  • 第 14 步初始化开始位置,也就是变量 k
  • 第 15 步中进行元素位置移动,如果添加则会不断把元素添加到元素内
  • 第 16 步中计算 length,元素长度是由原始长度减去删除元素加上增加元素的数量。
  • 最后一步就是返回在第 9 步中得到移除的元素 A.

splice 方法中,我们会使用情况如下:

  • 删除元素
  • 添加元素
  • 删除同时添加元素

删除元素在第 9 步处理删除元素数组,第 12 步处理元素前移并删除结尾的元素。添加元素在 13 步内处理元素后移,并在 15 步在对应下标下放入元素。删除元素同时删除上面每一步都会走到。

关于规范一些内部方法说明:
[[HasProperty]](P) 对象上的内部方法,若通过 P 得到对象结果为 undefined 则为 false,否则为 true。
[[GET]](P) 对象内部方法,通过属性名 P 获取结果。
[[Put]](P, V, Throw) 设置对象属性 P 的值为 V,Throw 若为 true,遇到错误则会抛出 TypeError。
[[Delete]](P, Throw) 删除对象上属性 P,若 Throw 为 true,遇到错误则抛出 TypeError.

图示一个数组删除数量多于添加数量

操作数组:

[0, 1, 2, 3]

这里进行 splice(1, 2, 4) 操作,从下标 1 位置移除 2 个元素,并添加一个元素

图示一个数组添加多余删除个数

[0, 1, 2, 3]

进行 splice(1, 1, 5, 6) 操作,从下标 1 的位置删除一个元素,并插入 5 和 6 的示例。

总结

通过上面规范分析和图示分析,其实 splice 之所以”慢“, 是因为每次 splice 操作除了需要分配新的内存区域去存储数据外,还需要不断操作元素的下标,大量移动元素位置,若 start=0,岂不是每个元素都需要移动一次呢?这就是说效率不高原因。

tips: splice 会修改数组本身,所以在 vue 和 react 中数组数据变化不会导致 UI 变化的原因之一。

其他参考:

https://www.jianshu.com/p/483…

更好的插入或者删除方式

上面说 splice 用于三种情况:

  • 删除元素
  • 添加元素
  • 删除同时添加元素

在最新的 ecma 中有新的方法可以替代 splice 用途

删除数组

使用 filter 代替:

const arr = [1, 2, 3, 4]
// 删除下标为 2 的元素
const newArray = arr.filter((_, index) => index !== 2)
// [1, 2, 4]

添加元素

使用 concat 或者 spread 代替配合 slice 实现任何位置插入:

const array = [1, 2, 3,]
// 下标 1 插入 10
const newArray = array.slice(0, 1).concat(10, array.slice(1))
// [1, 10, 2, 3]
// const newArray = [...array.slice(0, 1), 10, array.slice(1)]

添加删除元素

同上

const array = [0, 1, 2, 3, 4, 5]
// 下标位置 2 位置删除 2 个,并插入 7,8,9
const newArray = array.slice(0, 2).concat([7, 8, 9], array.slice(4))
// [0, 1, 7, 8, 9, 4, 5]

最后

相信自己,总会有一个办法解决问题,代码性能也会一点点提高,欢迎交流。

正文完
 0