JavaScript 网页编程从入门到精通 (清华社"视频大讲堂"大系·网络开发视频大讲堂)
上QQ阅读APP看书,第一时间看更新

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>