这是我参与8月更文挑战的第9天,活动详情查看: 8月更文挑战
前言
业务代码开发多了,其实就是在写 if-else
与条件判断相比,循环语句对程序执行性能的影响更大。一个循环语句的循环执行次数直接影响程序的时间复杂度,如果代码中还存在缺陷导致循环不能及时停止,从而造成死循环,那么给用户带来的使用体验将会是非常糟糕的。本文记录,怎样编写循环语句能对性能产生有益的影响。
三种常规循环语句
JavaScript中循环语句的常见写法有三种,第一种是标准的for循环,这与大部分编程语言类似,包括初始化、循环结束条件、迭代语句及循环体四部分,代码示例如下:
// 标准for循环
for(let i = 0; i < length; i ++){
// 循环体
}
第二种和第三种分别是while循环和do-while循环,二者唯一的差别就是do-while循环会先执行一遍循环体,再去判断循环结束条件,代码示例如下:
// while循环
let i = 0
while(i < length){
// 循环体
i ++
}
// do-while 循环
do {
// 循环体
i ++
} while (i < length)
通常在使用这三种循环语句时,基本场景都是对数组元素进行遍历。从索引的第一个元素开始直到数组的最后一个元素结束,每次在执行循环判断时,都需要将当前的数组索引与数组长度进行比较。由于该比较操作执行的过程中数组长度一般不会改变,且存取局部变量要比查找属性值更省时,所以提前将要遍历的数组长度声明为局部变量,然后将该局部变量进行循环结束的条件判断,效率会更高一些。下面以for循环为例:
// 较差的循环结束判断
const array = [1,2,3,4,5]
for(let i = 0; i < array.length; i ++){
// 省略循环体过程
}
// 较好的循环结束判断
const len = array.length
for(let i = 0; i < len; i ++){
// 省略循环体过程
}
这在对包含较大规模DOM节点数的遍历过程中,效果会更加明显。此外还有一种更简单的提升循环语句性能的方式:将循环变量递减到0,而非递增到数组总长度
// 更好的循环结束判断
for(let i = arr.length - 1; i > 0; i --){
// 省略循环体过程
}
因为循环结束的判断是和常量0进行比较的,不存在对数组长度属性值的查找或局部变量的读取,其比较的运算速度会更快。由于三种循环语句的执行性能基本类似,所以仅针对结束条件的判断进行优化。
for-in循环与函数迭代
for-in可用来遍历JavaScript对象的可枚举属性,通常用法如下:
// 遍历object对象的所有属性
for(let prop in object){
// 确保不会遍历到object原型链上的其他对象
if(object.hasOwnProperty(prop)){
// 相关属性的处理过程
}
}
可以看出for−in循环能够遍历对象的属性集,特别适合处理诸如JSON对象这样的未知属性集,但对通常的循环使用场景来说,由于它遍历属性的顺序不确定,循环的结束条件也无法改变,并且因为需要从目标对象中解析出每个可枚举的属性,即要检查对象的原型和整个原型链,所以其循环速度也会比其他循环方式要慢许多,如果循环性能有要求则尽量不要使用for−in循环。
另外对于数组的循环,JavaScrpt原生提供了一种forEach函数选代的方法,此方法会遍历数组上的所有元素,并对每个元素执行一个方法,所运行的方法作为forEach函数的入参,代码如下:
//对数组进行函数选代
myArray.forEach((value, index, arr) => {
// 可处理数组中的每个元素
})
这种方法使用起来的确会让数组元素的迭代看起来更加直观,但在通常情况下与三种基本的循环方法相比,其性能方面仅能达到后者的1/8,如果数组长度较大或对运行速度有比较严格的要求,则函数迭代的方式不建议使用。
同时还有一种for语句的变形,就是ES6加入的for−of循环,我们可以使用它来代替for-in和forEach循环,它不仅在性能方面比这二者更好,并且还支持对任何可选迭代的数据结构进行遍历,比如数组、字符串、映射和集合,但与三种常规循环语句相比其性能还是稍逊色一些的。