首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议29:准确使用循环体

关灯直达底部

1.选择正确的循环体

在大多数编程语言中,代码执行时间多数消耗在循环的执行上。在一系列编程模式中,循环是最常用的模式之一,因此也是提高性能必须关注的地方之一。理解JavaScript中循环对性能的影响至关重要,因为死循环或长时间运行的循环会严重影响用户体验。JavaScript定义了4种类型的循环:

第一种循环是标准的for循环,与C语言使用同样的语法:


for(var i=0;i<10;i++){

//循环体

}


for循环是最常用的循环结构,它由4部分组成:初始化体、前测条件、后执行体、循环体。当遇到一个for循环时,初始化体首先执行,然后进入前测条件。如果前测条件的计算结果为true,则执行循环体,然后运行后执行体。for循环在封装上的直接性是开发者喜欢使用它的原因之一。

第二种循环是while循环。while循环是一个简单的前测循环,由一个前测条件和一个循环体构成:


var i=0;

while(i<10){

//循环体

i++;

}


在执行循环体之前,先对前测条件进行计算。如果计算结果为true,那么就执行循环体;否则将跳过循环体。任何for循环都可以写成while循环,反之亦然。

第三种循环是do while循环。do while循环是JavaScript中唯一一种后测试的循环,它包括两部分:循环体和后测条件。


var i=0;

do{

//循环体

}while(i++<10);


在一个do while循环中,循环体至少运行一次,后测条件决定循环体是否应再次执行。

第四种循环是for in循环。此循环有一个非常特殊的用途:可以枚举任何对象的命名属性。


for(var prop in object){

//循环体

}


每次执行循环,属性都被对象属性的名字(一个字符串)填充,直到所有的对象属性遍历完成才返回。返回的属性包括对象的实例属性和对象从原型链继承而来的属性。

提高循环性能的起点是选用哪种循环。在JavaScript提供的4种循环类型中,只有for in循环执行速度比其他循环明显要慢。

由于每次迭代操作要搜索实例或原型的属性,for in循环每次迭代都要付出更多开销,因此它比其他类型循环执行速度慢一些。在同样的循环迭代操作中,其他类型循环执行速度比for in循环快7倍之多,因此推荐这样做:除非需要对数目不详的对象属性进行操作,否则避免使用for in循环。例如,迭代遍历一个有限的、已知的属性列表,使用其他循环类型更快,具体的使用模式如下:


var props=[/"prop1/",/"prop2/"],

i=0;

while(i<props.length){

process(object[props[i]]);

}


此代码创建一个由成员和属性名构成的队列。while循环用于遍历这几个属性并处理所对应的对象成员,而不是遍历对象的每个属性。此代码只关注感兴趣的属性,节约了循环时间。

2.比较for和while

除for in循环外,其他循环类型的性能相当,难以确定哪种循环执行速度更快。选择循环类型应基于需求而不是性能。

可以通过设计for和while循环来完成特定动作的重复性操作。下面分别从语义性、思维模式、达成目标这3个角度来分析如何正确选用while和for循环。

(1)从语义性角度比较

for和while循环可以按如下模式进行相互转换:


for(initialization;test;increment)//声明并初始化循环变量、循环条件、递增循环变量

statements//可执行的循环语句


相当于:


initialization;//声明并初始化循环变量

while(test){//循环条件

statement//可执行的循环语句

increment;//递增循环变量

}


for循环是以循环变量的变化来控制循环进程的,即for循环的整个循环流程是预先计划好的,虽然中途可能会因存在异常或特别情况而退出循环,但是循环的规律性是有章可循的。这样我们能够很容易地预知循环的次数、每次循环的状态等信息。

while循环根据特定条件来决定循环操作,由于这个条件是动态的,无法预知条件何时为true或false,因此该循环的循环操作就具有很大的不确定性,每一次循环时都不知道下一次循环的状态如何,只能通过条件的动态变化来确定。

因此,for结构常常被用于有规律的重复操作中,如对数组、对象、集合等的操作。当然,对于这些对象的迭代操作,更适合使用for in这种特殊的for循环来操作,因为它提供了更大的便利,可以防止错误的发生。while循环更适合用于待定条件的重复操作,以及依据特定事件控制的循环等操作。

(2)从思维模式角度比较

for循环和while循环在思维模式上也存在差异。在for循环中,将循环的三要素(起始值、终止值和步长)定义为3个基本表达式作为结构语法的一部分固定在for语句内,使用小括号进行语法分隔,这与while循环中while语句内仅是条件检测的表达式截然不同,这样更有利于JavaScript进行快速预编译。

因此,当阅读到for结构的第一行代码时,就能够获取整个循环结构的控制方式,然后再根据上面的表达式来决定是否执行下面的循环体内的语句。可以这样概括,for结构适合简单的数值迭代操作。例如,快速阅读下面示例的代码:


for(var n=1;n<10;n++){//循环操作的环境条件

alert(n);//循环操作的语句

}


之后可以按以下方式对逻辑思维进行总结。

执行循环条件:1<n<10、步长为n++。

执行循环语句:alert(n)。

这种把循环操作的环境条件和循环操作语句分离开的设计模式能够提高程序的执行效率,同时也避免了因为把循环条件与循环语句混在一起而造成的遗漏或错误。描述这种思维模式的简化示意图如图1.3所示。

图 1.3 for结构的数值迭代计算

如果for循环的循环条件比较复杂,不是简单的数值迭代,这时for语句就必须考虑如何把循环条件和循环语句联系起来才可以正确地执行整个for循环。因此,根据for结构的运算顺序,for语句首先计算第一个和第二个表达式,然后执行循环体语句,最后返回执行for语句的第三个表达式,如此循环执行。例如:


for(var a=true,b=1;a;b++){

if(b>9)//在循环体内间接计算迭代的步长

a=false;

alert(b);

}


在上面的这个示例中,for语句的第三个表达式不是直接计算步长的,整个for循环也没有明确告知循环步长的表达式,如果要确定迭代的步长,就必须依据循环体内的语句。因此,整个for结构的逻辑思维就存在一个回旋的过程,如图1.4所示。

图 1.4 for结构的条件迭代计算

for循环的特异性导致在执行复杂条件时效率会大大降低。相对而言,while循环天生就是为复杂的条件而设计的,它将复杂的循环控制放在循环体内执行,而while语句自身仅用于测试循环条件,这样就避免了结构的分隔和逻辑的跳跃。例如,使用while结构来表示这种复杂的条件循环的代码如下,这种思维变化的示意图如图1.5所示。


var a=true,b=1;while(a){//在循环体内间接计算迭代

if(b>9)

a=false;

alert(b);

b++;

}


图 1.5 while结构的条件计算

(3)从达成目标的角度比较

有些循环的循环次数在循环之前就可以预测,如计算1~100的数字和。而有些循环具有不可预测性,无法事先确定循环的次数,甚至无法预知循环操作的趋向,这些构成了在设计循环结构时必须考虑的达成目标需要解决的问题。即使是相同的操作,如果达成目标的角度不同,可能重复操作的设计也就不同。例如,统计全班学生的成绩和统计合格学生的成绩就是两个不同的达成目标。一般来说,在循环结构中动态改变循环变量的值时建议使用while结构,而对于静态的循环变量,则可以考虑使用for结构。