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的处理能力,这时我们就需要寻求该算法的一种迭代式解决方案了。任何可以被递归定义的函数,都可以被改写为迭代式的程序,要将这点牢记于心。