数据结构与算法JavaScript描述
上QQ阅读APP看书,第一时间看更新

1.2 JavaScript编程实践

本节将讨论如何使用JavaScript。我们知道,每个程序员编写程序的风格和惯例都不尽相同,因此在本书一开始,我想先说说我自己的编程风格和惯例,这样读者在后续章节中碰到更复杂一点的程序时,就不会感到疑惑了。本书并非一部JavaScript新手教程,而是语言基本结构使用方法指南。

1.2.1 声明和初始化变量

JavaScript中的变量默认是全局变量,严格地说,甚至不需要在使用前进行声明。如果对一个事先未予声明的JavaScript变量进行初始化,该变量就成了一个全局变量。但本书遵循C++和Java等编译型语言的习惯,在使用变量前先对其进行声明。这样做的好处是,声明的变量都是局部变量。本章稍后部分将详细讨论变量的作用域。

在JavaScript中声明变量,需使用关键字var,后跟变量名,后面还可以跟一个赋值表达式。下面是一些例子:

        var number;
        var name;
        var rate = 1.2;
        var greeting = "Hello, world! ";
        var flag = false;

1.2.2 JavaScript中的算术运算和数学库函数

JavaScript使用标准的算术运算符:

• +(加)

• -(减)

• *(乘)

• /(除)

• %(取余)

JavaScript同时拥有一个数学库,用来完成一些高级运算,比如平方根、绝对值和三角函数。算术运算符遵循标准的运算顺序,可以用括号来改变运算顺序。

例1-1 演示了使用JavaScript执行一些算术运算的例子,同时也用到了一些数学库中的函数。

例1-1 JavaScript中的算术运算和数学函数

        var x = 3;
        var y = 1.1;
        print(x + y);
        print(x * y);
        print((x+y)*(x-y));
        var z = 9;
        print(Math.sqrt(z));
        print(Math.abs(y/x));

这段程序的输出为:

        4.1
        3.3000000000000003
        7.789999999999999
        3
        0.3666666666666667

如果计算精度不必像上面那样精确,可以将数字格式化为固定精度:

        var x = 3;
        var y = 1.1;
        var z = x * y;
        print(z.toFixed(2)); //显示3.30

1.2.3 判断结构

根据布尔表达式的值,判断结构让程序可以选择执行哪些程序语句。本书用到的两种判断结构为if语句和switch语句。

if语句有如下三种形式:

• 简单的if语句;

• if-else语句;

• if-else if语句。

例1-2 演示了如何编写简单的if语句。

例1-2 简单的if语句

        var mid = 25;
        var high = 50;
        var low = 1;
        var current = 13;
        var found = -1;
        if (current < mid) {
          mid = (current-low) / 2;
        }

例1-3 演示了if-else语句。

例1-3 if-else语句

        var mid = 25;
        var high = 50;
        var low = 1;
        var current = 13;
        var found = -1;
        if (current < mid) {
          mid = (current-low) / 2;
        }
        else {
          mid = (current+high) / 2;
        }

例1-4 演示了if-else if语句。

例1-4 if-else if语句

        var mid = 25;
        var high = 50;
        var low = 1;
        var current = 13;
        var found = -1;
        if (current < mid) {
          mid = (current-low) / 2;
        }
        else if (current > mid) {
          mid = (current+high) / 2;
        }
        else {
          found = current;
        }

本书用到的另外一个判断结构是switch语句。在有多个简单的选择时,使用该语句的代码结构更加清晰。例1-5演示了switch语句的工作原理。

例1-5 switch语句

        putstr("Enter a month number: ");
        var monthNum = readline();
        var monthName;
        switch (monthNum) {
          case "1":
              monthName = "January";
              break;
          case "2":
              monthName = "February";
              break;
          case "3":
              monthName = "March";
              break;
          case "4":
              monthName = "April";
              break;
          case "5":
              monthName = "May";
              break;
          case "6":
              monthName = "June";
              break;
          case "7":
              monthName = "July";
              break;
          case "8":
              monthName = "August";
              break;
          case "9":
              monthName = "September";
              break;
          case "10":
            monthName = "October";
            break;
          case "11":
            monthName = "November";
            break;
          case "12":
            monthName = "December";
            break;
          default:
            print("Bad input");
        }

这是解决该问题最高效的方式吗?不是,但是这个例子充分展示了switch语句的工作原理。

JavaScript中的switch语句和其他编程语言的一个主要区别是:在JavaScript中,用来判断的表达式可以是任意类型,而不仅限于整型;而C++和Java等一些语言就要求该表达式必须为整型。事实上,如果你留意观察,上面那个例子中代表月份的数字其实是字符串类型。不用将它们转化成整型,就可以直接在switch语句中使用。

1.2.4 循环结构

本书涉及的多数算法,从本质上都具有循环的特性。本书将用到两种循环结构:while循环和for循环。

如果希望在条件为真时执行一组语句,就选择while循环。例1-6展示了while循环的工作原理。

例1-6 while循环

        var number = 1;
        var sum = 0;
        while (number < 11) {
          sum += number;
          ++number;
        }
        print(sum); //显示55

如果希望按执行次数执行一组语句,就选择for循环。例1-7使用for循环求整数1到10的累加和。

例1-7 使用for循环求和

        var number = 1;
        var sum = 0;
        for (var number = 1; number < 11; number++) {
          sum += number;
        }
        print(sum); //显示55

访问数组中的元素时,也经常用到for循环,如例1-8所示。

例1-8 使用for循环访问数组

        var numbers = [3, 7, 12, 22, 100];
        var sum = 0;
        for (var i = 0; i < numbers.length; ++i) {
          sum += numbers[i];
        }
        print(sum); //显示144

1.2.5 函数

JavaScript提供了两种定义函数的方式,一种有返回值,一种没有返回值(这种函数有时也叫做子程或void函数)。

例1-9 展示了如何定义一个有返回值的函数和如何在JavaScript中调用该函数。

例1-9 有返回值的函数

        function factorial(number) {
          var product = 1;
          for (var i = number; i >= 1; ——i) {
              product *= i;
          }
          return product;
        }
        print(factorial(4)); //显示24
        print(factorial(5)); //显示120
        print(factorial(10)); //显示3 628 800

例1-10 展示了如何定义一个没有返回值的函数,使用该函数并不是为了得到它的返回值,而是为了执行函数中定义的操作。

例1-10 JavaScript中的子程或者void函数

        function curve(arr, amount) {
          for (var i = 0; i < arr.length; ++i) {
              arr[i] += amount;
          }
        }
        var grades = [77, 73, 74, 81, 90];

        curve(grades, 5);
        print(grades); //显示82,78,79,86,95

JavaScript中,函数的参数传递方式都是按值传递,没有按引用传递的参数。但是JavaScript中有保存引用的对象,比如数组,如例1-10所示,它们是按引用传递的。

1.2.6 变量作用域

变量的作用域是指一个变量在程序中的哪些地方可以访问。JavaScript中的变量作用域被定义为函数作用域。这是指变量的值在定义该变量的函数内是可见的,并且定义在该函数内的嵌套函数中也可访问该变量。

在主程序中,如果在函数外定义一个变量,那么该变量拥有全局作用域,这是指可以在包括函数体内的程序的任何部分访问该变量。下面用一段简短的程序展示全局作用域的工作原理:

        function showScope() {
          return scope;
        }
        var scope = "global";

        print(scope); //显示"global"
        print(showScope()); //显示"global"

函数showScope()可以访问变量scope,因为scope是一个全局变量。可以在程序的任意位置定义全局变量,比如在函数定义前或者函数定义后。

在showScope()函数内再定义一个scope变量,看看这时发生了什么:

        function showScope() {
          var scope = "local";
          return scope;
        }

        var scope = "global";
        print(scope); //显示"global"
        print(showScope()); //显示"local"

showScope()函数内定义的变量scope拥有局部作用域,而在主程序中定义的变量scope是一个全局变量。尽管两个变量名字相同,但它们的作用域不同,在定义它们的地方访问时得到的值也不一样。

这些行为都是正常且符合预期的。但是,如果在定义变量时省略了关键字var,那么一切都变了。JavaScript允许在定义变量时不使用关键字var,但这样做的后果是定义的变量自动拥有了全局作用域,即使你是在一个函数内定义该变量,它也是全局变量。

例1-11 展示了定义变量时省略了关键字var的后果。

例1-11 滥用全局变量的恶果

        function showScope() {
          scope = "local";
          return scope;
        }

        scope = "global";
        print(scope); //显示"global"
        print(showScope()); //显示"local"
        print(scope); //显示"local"

在例1-11中,由于在showScope()函数内定义变量scope时省略了关键字var,所以在将字符串"local"赋给该变量时,实际上是改变了主程序中scope变量的值。因此,在定义变量时,应该总是以关键字var开始,以避免发生类似的错误。

前面我们提到,JavaScript拥有的是函数作用域,其含义是JavaScript中没有块级作用域,这一点有别于其他很多现代编程语言。使用块级作用域,可以在一段代码块中定义变量,该变量只在块内可见,离开这段代码块就不可见了,在C++或者Java的for循环语句中,经常可以看到这样的例子:

        for (int i = 1; i <= 10; ++i) {
          cout << "Hello, world! " << endl;
        }

虽然JavaScript没有块级作用域,但在本书中编写for循环语句时,我们假设它有:

        for (var i = 1; i <= 10; ++i ) {
          print("Hello, world! ");
        }

这样做的原因是,我们不希望自己成为你养成坏编程习惯的帮手。

1.2.7 递归

JavaScript中允许函数递归调用。前面定义过的factorial()函数也可以用递归方式定义:

        function factorial(number) {
          if (number == 1) {
              return number;
          }
          else {
              return number * factorial(number-1);
          }
        }
        print(factorial(5));

当一个函数被递归调用,在递归没有完成时,函数的计算结果暂时被挂起。为了说明这个过程,这里用一幅图展示了以5作为参数,调用factorial()函数时函数的执行过程:

        5 * factorial(4)
        5 * 4 * factorial(3)
        5 * 4 * 3 * factorial(2)
        5 * 4 * 3 * 2 * factorial(1)
        5 * 4 * 3 * 2 * 1
        5 * 4 * 3 * 2
        5 * 4 * 6
        5 * 24
        120

本书讨论的一些算法采用了递归的方式。对于大多数情况,JavaScript都有能力处理递归层次较深的递归调用(上面的例子递归层次较浅);但是保不齐有的算法需要的递归深度超出了JavaScript的处理能力,这时我们就需要寻求该算法的一种迭代式解决方案了。任何可以被递归定义的函数,都可以被改写为迭代式的程序,要将这点牢记于心。