【JavaScript】清空数组元素,哪种方式效率更好?【length=0 】VS 【splice(0,len)】(补充)

637 阅读3分钟

前文回顾:
使用 array.sort(() => number) 实现数组的各种排序,支持对象的排序

以前清空数组使用 length = 0,现在清空数组可以使用 splice,那么哪种方式效率更好呢?我们来看看内部的执行步骤。

如何看内部执行步骤呢?我们可以借用一下 reactive,因为他使用 Proxy 对数组的各种操作进行了拦截。

设置断点

找到 reactive 的源码,分别在 get、set、has、deleteProperty 等处设置断点:

Proxy设置断点.png

这样就可以看看内部的执行情况了。

length = 0

先看看 length = 0 的情况:

  const arr = reactive([1,20])
  const mydel = () => {
    arr.length = 0
    console.log('arr', arr)
  }

跟踪断点,看看执行步骤:

  • set,length,0:修改 length 的属性值,调用原型,数组被清空了;(好突然)
  • 【没有触发 deleteProperty】,没有触发就是没有被执行吗?

push 的步骤

  • get,push,
  • get,length
  • set,依次添加
  • set,length

步骤很简单,上来就是改 length 的值,然后数组就被清空了,只是 deleteProperty 并没有被触发,那么数组是怎么空的?

考虑到是拦截 set 后,使用 Reflect.set 调用原型,那么这种情况下,还会继续被拦截吗?

splice

  const mydel2 = () => {
    const re = arr.splice(0, arr.length)
    console.log('arr', arr)
    console.log('re', re)
  }

再看看 splice 的执行顺序:

  • get,splice ,调用原型,无变化;
  • get,length,调用原型,返回 长度(4);// 第二个参数使用了length属性
  • get,length,又被执行一次;
  • get,constructor,调用原型; // 创建新数组,存放被删除的元素
  • has,0,返回 true; // 开始拷贝
  • get,0,返回第一个数组元素的值;
  • has,1,返回 true;
  • get,1,返回第一个数组元素的值;
  • 重复 length 次;
  • deleteProperty,3 (最后一个),调用原型删除最后一个数组元素;// 开始删除元素
  • 重复 length 次,从后往前依次删除数组元素;
  • set,length,0,调用原型,修改 属性值;

这就复杂多了,为啥呢?考虑到 splice 的功能,参考执行步骤可以猜猜:

  • 首先,splice 不仅仅是删除元素,还会把被删除的数组元素返回,组成新的数组,所以,不能删了就完事了,需要先拷贝。
  • 而每次获取元素前,需要先判断是否有元素(has),然后才能拷贝(get)元素。
  • 然后不能全删,splice 可以删除一部分数组元素,所以需要从后往前一个元素一个元素删除。
  • 删除完成之后,才会修改 length 属性。

清空后添加会怎么样?

上面只考虑了清空的情况,一般情况下我们清空数组后,还会添加新的数组成员,那么 splice 又会如何?

const re = arr.splice(0, len, ...[99,88])
  • 前面拷贝数组的步骤是一样的
  • 然后没有着急删除元素,而是对比新旧数组的 length :
    • 如果新、旧数组的 length 一致,那么不删除元素空间,也不添加元素空间,直接依次修改元素值;
    • 如果新数组更长,那么依次修改完前面的元素空间后,会在数组最后增加新的元素空间,存放新值;
    • 如果旧数组长,那么先依次删除后面“多余”的元素空间,然后依次修改值;
  • set,length,修改新值。

小结

splice 非常灵活,可以删除部分数组元素,也可以添加新的数组元素,只是用来清空数组的话,有点大材小用了。

而 length = 0 就是简单粗暴的方法,既然要清空,那么直接归零就好。即使也是一个元素一个元素删除的,那么也没有拷贝数组的步骤。

如果清空后要添加新数组成员的话,使用 splice 可以避免删除一些元素空间,但是前面的拷贝步骤还是有的,即使不接收返回值,也依旧会拷贝。

  • 如果数组比较长,那么拷贝的成本会有点高,虽然只是拷贝地址,但是至少也要遍历一下。
  • 不知道 length = 0 到底是怎么删除的。