6.9 案例实战
下面结合具体案例讲解各种语句在开发中的应用技巧。
6.9.1 编程题
1.编写函数输出1~10000之间的所有对称数。
提示:对称数就是把一个数字倒着读仍然和原数字相同的数字,如121、1331等。
参考:
function symmetry(num) { var arr = []; while(--num > 10) { //依次把每个值分开为多个数字组成的数组,然后颠倒顺序,再重新连接为一个数字字符串 var reverseNum = num.toString().split('').reverse().join(''); //比较颠倒后的值是否与原值相同,如果相同则存储到临时数组中 (reverseNum == num) && (arr.push(num)); } return arr; } var result = symmetry(10000); console.log(result);
2.实现乱序函数randomSort(array),能够将数组元素打乱存储,如[1,2,3,4,5],输出为[3,2,4,5,1]。要求N次以内数组元素顺序不重复。
参考:
function randomSort(array) { var n = array.length, t, i; while (n) { i=Math.random()*n--|0; //获取数组长度内一个随机数 t=array[n]; //依次取出数组元素的值 array[n]=array[i]; //与数组随机位置的元素值进行互换 array[i] = t; } return array; }
3.实现随机选取10~100之间的10个数字,存入一个数组,并排序。
参考:
var iArray = []; function getRandom(istart, iend){ //随机选取函数 var iChoice = istart - iend +1; return Math.floor(Math.random() * iChoice ) + istart; } for(var i=0; i<10; i++){ //调用随机选取函数获取10个随机值,然后存入一个数组中 iArray.push(getRandom(10,100)); } iArray.sort(); //数组排序
4.分别用while语句和for语句编写1+2+…+100的求和程序?
参考:
//while var sum = 0, i = 1; while(i <= 100){ sum+=i++; //等价于sum=sum+i; i=i+1; } document.write('1+2+3+...+100='+sum); //5050 //for var sum = 0; for(i = 1; i <= 100; i++){ sum += i; } document.write('1+2+3+...+100='+sum); //5050
6.9.2 提升条件检测性能
在JavaScript中查表法可通过数组或普通对象实现,查表法访问数据比if和switch更快,特别是当条件体的数目很大时。与if和switch相比,查表法不仅非常快,而且当需要测试的离散值数量非常大时,有助于保持代码的可读性。
【示例1】在下面的代码中,使用switch检测value值。
function map(value){ switch(value) { case 0: return "result0"; case 1: return "result1"; case 2: return "result2"; case 3: return "result3"; case 4: return "result4"; case 5: return "result5"; case 6: return "result6"; case 7: return "result7"; case 8: return "result8"; case 9: return "result9"; default: return "result10"; } }
【示例2】使用switch语句检测value值的方法比较笨拙,针对上面的代码可以使用一个数组查询替代switch结构块。下面的代码把所有可能值存储到一个数组中,然后通过数组下标快速检测元素的值。
function map(value){ var results = ["result0", "result1", "result2", "result3", "result4", "result5", "result6", "result7", "result8","result9", "result10"] return results[value]; }
使用查表法可以消除所有条件判断,由于没有条件判断,当候选值数量增加时,基本上不会增加额外的性能开销。
查表法常用于一个键和一个值形成逻辑映射的领域,而switch更适合于每个键需要一个独特的动作或一系列动作的场合。
【示例3】如果条件查询中键名不是有序数字,则无法与数组下标映射,这时可以使用对象成员查询。
function map(value){ var results = { "a":"result0", "b":"result1", "c":"result2", "d": "result3", "e":"result4", "f": "result5", "g":"result6", "h":"result7","i":"result8", "j":"result9", "k":"result10" } return results[value]; }
6.9.3 提升循环迭代性能
每次运行循环体时都会产生性能开销,增加总的运行时间,即使是循环体中最快的代码,累计迭代上千次,也将带来不小的负担。因此,减少循环的迭代次数可获得显著的性能提升。
有两个因素影响到循环的性能:
每次迭代做什么。
迭代的次数。
通过减少这两者中一个或全部的执行时间,可以提升循环的整体性能。如果一次循环需要较长时间来执行,那么多次循环将需要更长时间。限制在循环体内进行耗时操作的数量是一个加快循环的好方法。
【示例1】一个典型的数组处理循环可采用3种循环中的任何一种。
//方法1 for (var i=0; i < items.length; i++){ process(items[i]); } //方法2 var j=0; while (j < items.length){ process(items[j++]); } //方法3 var k=0; do { process(items[k++]); } while (k < items.length);
在每个循环中,每次运行循环体都要发生如下操作:
第1步,在控制条件中读一次属性(items.length)。
第2步,在控制条件中执行一次比较(i < items.length)。
第3步,比较操作,观察条件控制体的运算结果是不是true(i < items.length == true)。
第4步,一次自加操作(i++)。
第5步,一次数组查找(items[i])。
第6步,一次函数调用(process(items[i]))。
在这些简单的循环中,即使没有太多的代码,每次迭代也都要进行这6步操作。代码运行速度在很大程度上是由process()对每个项目的操作所决定,即便如此,减少每次迭代中操作的总数也可以大幅度提升循环的整体性能。
优化循环的第一步是减少对象成员和数组项查找的次数。在大多数浏览器上,这些操作比访问局部变量或直接量需要更长的时间。例如,在上面的代码中,每次循环都查找items.length,这是一种浪费,因为该值在循环体执行的过程中不会改变,因此产生了不必要的性能损失。
【示例2】可以简单地将此值存入一个局部变量中,在控制条件中使用这个局部变量,从而提升了循环性能:
for (var i=0, len=items.length; i < len; i++){ process(items[i]); } var j=0, count = items.length; while (j < count){ process(items[j++]); } var k=0, num = items.length; do { process(items[k++]); } while (k < num);
这些重写后的循环只在循环执行之前对数组长度进行一次属性查询,使控制条件中只有局部变量参与运算,所以速度更快。根据数组的长度,在大多数浏览器上总循环时间可以节省大约25%,在IE浏览器中可节省50%。
还可以通过改变循环的顺序来提高循环性能。通常,数组元素的处理顺序与任务无关,可以从最后一个开始,直到处理完第一个元素。倒序循环是编程语言中常用的性能优化方法,不过一般不太容易理解。
【示例3】在JavaScript中,倒序循环可以略微提升循环性能:
for (var i=items.length; i--; ){ process(items[i]); } var j = items.length; while (j--){ process(items[j]); } var k = items.length-1; do { process(items[k]); } while (k--);
在上面的代码中使用了倒序循环,并在控制条件中使用了减法。每个控制条件只是简单地与0进行比较。控制条件与true值进行比较,任何非零数字自动强制转换为true,而0等同于false。
实际上,控制条件已经从两次比较减少到一次比较。将每个迭代中的两次比较减少到一次可以大幅度加快循环速度。通过倒序循环和最小化属性查询,可以看到执行速度比原始版本提升了50%~60%。与原始版本相比,每次迭代中只进行如下操作:
第1步,在控制条件中进行一次比较(i == true)。
第2步,一次减法操作(i--)。
第3步,一次数组查询(items[i])。
第4步,一次函数调用(process(items[i]))。
在新循环的每次迭代中减少两个操作,随着迭代次数的增多,性能将显著提升。
6.9.4 设计杨辉三角
杨辉三角是一个经典、有趣的编程案例,它揭示了多次方二项式展开后各项系数的分布规律,如图6-7所示。
图6-7 高次方二项式开方之后各项系数的数表分布规律
从杨辉三角形的特点出发,可以总结出下面两点运算规律。
设起始行为第0行,第N行有N+1个值。
设N>=2,对于第N行的第J个值:
➢ 当J=1或J=N+1时,其值为1。
➢ J! =1且J! =N+1时:其值为第N-1行的第J-1个值与第N-1行第J个值之和。
使用递归算法可以求指定行和列交叉点的值,具体设计函数如下:
function c(x, y){ //求指定行和列的数字,参数x表示行数,参数y表示列数 if((y==1)||(y==x+1))return 1; //如果是第一列或最后一列,则取值为1 return c(x-1, y-1)+c(x-1, y); //通过递归算法求指定行和列的值,x-1表示上一行,返回上一行中第y-1列与第y列值之和 }
然后输出每一行每一列的数字:
for(var i=0; i<=n; i++){ //遍历幂数 for(var j=1; j<i+2; j++){ //遍历每一列 print(c(i, j)); //调用求值函数,输出每一个数字 } print("<br/>"); //换行 }
使用递归算法,思路比较清晰,代码简洁,但是它的缺点也很明显:执行效率是非常低的,特别是幂数很大时,其执行速度异常缓慢,甚至于死机。所以,我们有必要对其算法做进一步的优化。
优化设计:
定义两个数组,数组1为上一行数字列表,为已知数组;数组2为下一行数字列表,为待求数组。假设上一行数组为[1,1],即第二行数字。那么,下一行数组的元素值就等于上一行相邻两个数字的和,即为2,然后数组两端的值为1,这样就可以求出下一行数组,即第三行数字列表。求第四行数组的值,可以把已计算出的第三行数组作为上一行数组,而第四行数组为待求的下一行数组,依此类推。
实现上述算法,可以使用双层循环嵌套结构,外层循环结构遍历高次方的幂数(即行数),内层循环遍历每次方的项数(即列数),实现的核心代码如下:
var a1=[1,1]; //上一行数组,初始化为[1,1] var a2=[1,1]; //下一行数组,初始化为[1,1] for(var i=2; i<=n; i++){ //从第3行开始遍历高次方的幂数,n为幂数 a2[0]=1; //定义下一行数组的第一个元素为1 for(var j=1; j<i-1; j++){ //遍历上一行数组,并计算下一行数组中间的数字 a2[j] = a1[j -1] + a1[j]; } a2[j]=1; //定义下一行数组的最后一个元素为1 for(var k=0; k<=j; k++){ //把上一行数组的值传递给上一行数组,从而实现交替循环 a1[k] = a2[k]; } }
完成算法设计之后,就可以设计输出数表,完整代码如下,演示效果如图6-8所示。
图6-8 9次幕杨辉三角数表分布图
<!doctype html> <html> <head> <meta charset="utf-8"> <title>输出杨辉三角</title> <script type="text/javascript"> function print(v){ //输出函数 //如果传递值为输出的数字,则包含在一个<span>标签中,以方便CSS控制 if(typeof v == "number"){ var w=40; //默认<span>标签宽度 if(n>30)w=(n-30)+40; //根据幂数的增大,适当调整<span>标签的宽度 var s = '<span style="padding:4px 2px; display:inline-block; text-align:center; width:'+ w +'px; ">' + v +'</span>'; document.write(s); //在页面中输出字符串 } else{ //如果参数值为字符串,说明是输出其他字符串 document.write(v); //则调用document对象的write()方法直接输出 } } //输入接口,用来接收用户设置幂数 var n=prompt("请输入幂数:",9); //默认值为9 n=n-0; //把输入值转换为数值类型 var t1 = new Date(); var a1=[1,1], a2=[1,1]; //声明并初始化数组 print('<div style="text-align:center; ">'); //输出一个包含框 print(1); //输出第一行中的数字 print("<br />"); for(var i=2; i<=n; i++){ //从第三行开始,遍历每一行 print(1); //输出每一行中第一个数字 for(var j=1; j<i-1; j++){ //从第2个数字开始,遍历每一行 a2[j] = a1[j -1] + a1[j]; print(a2[j]); //输出每一行中中间的数字 } a2[j]=1; //补上最后一个数组元素的值 for(var k=0; k<=j; k++){ //把上一行数组的值传递给下一行数组 a1[k] = a2[k]; } print(1); //输出每一行中最后一个数字 print("<br/>"); //输出换行符 } print("</div>"); //输出包含框的封闭标签 var t2 = new Date(); print("<p style='text-align:center; '>耗时为(毫秒):" + ( t2- t1) + "</p>" ); </script> </head> <body> </body> </html>