前文回顾:
使用 array.sort(() => number) 实现数组的各种排序,支持对象的排序
以前清空数组使用 length = 0,现在清空数组可以使用 splice,那么哪种方式效率更好呢?我们来看看内部的执行步骤。
如何看内部执行步骤呢?我们可以借用一下 reactive,因为他使用 Proxy 对数组的各种操作进行了拦截。
设置断点
找到 reactive 的源码,分别在 get、set、has、deleteProperty 等处设置断点:
这样就可以看看内部的执行情况了。
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 到底是怎么删除的。