在深入学习各种数据结构和算法前,让我们先大概了解一下JavaScript。本节教大家一些相关的基础知识,有利于学习后面各章。 首先来看在HTML中编写JavaScript的两种方式: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script> alert('Hello, World!'); </script> </body> </html> 第一种方式如上面的代码所示。创建一个HTML文件,把代码写进去。在这个例子里,我们在HTML中声明了script标签,然后把JavaScript代码都写进这个标签。 第二种方式,我们需要创建一个JavaScript文件(比如01-HelloWorld.js),在里面写入如下代码: alert('Hello, World!'); 然后,我们的HTML文件看起来如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <script src="01-HelloWorld.js"> </script> </body> </html> 第二个例子展示了如何将一个JavaScript文件引入HTML文件。 这两个例子,无论执行哪个输出都是一样的。但第二个例子是最佳实践。 可能你在网上的一些例子里看到过JavaScript的include语句,或者放在head标签中的JavaScript代码。作为最佳实践,我们会在关闭body标签前引入JavaScript代码。这样浏览器就会在加载脚本之前解析和显示HTML,有利于提升页面的性能。 1.2.1 变量 变量保存的数据可以在需要时设置、更新或提取。赋给变量的值都有对应的类型。JavaScript的类型有数字、字符串、布尔值、函数和对象。还有undefined和null,以及数组、日期和正则表达式。下面的例子介绍如何在JavaScript里使用变量。 var num = 1; //{1} num = 3; //{2} var price = 1.5; //{3} var name = 'Packt'; //{4} var trueValue = true; //{5} var nullVar = null; //{6} var und; //{7} 在行{1},我们展示了如何声明一个JavaScript变量(声明了一个数字类型)。虽然关键字var不是必需的,但最好每次声明一个新变量时都加上。 在行{2}, 我们更新了已有变量。JavaScript不是强类型语言。这意味着你可以声明一个变量并初始化成一个数字类型的值,然后把它更新成字符串或者其他类型的值,不过这并不是一个好做法。 在行{3},我们又声明了一个数字类型的变量,不过这次是十进制浮点数。在行{4}, 声明了一个字符串;在行{5},声明了一个布尔值;在行{6},声明了一个null;在行{7}, 声明了undefined变量。null表示变量没有值,undefined表示变量已被声明,但尚未赋值: console.log("num:" + num); console.log("name:" + name); console.log("trueValue:" + trueValue); console.log("price:" + price); console.log("nullVar:" + nullVar); console.log("und:" + und); 如果想看我们声明的每个变量的值,可以用console.log来实现,就像上面代码片段中那样。 书中示例代码会使用三种方式输出JavaScript的值。第一种是alert('My text here'),将输出到浏览器的警示窗口;第二种是console.log('My text here'),将把文本输出到调试工具的Console标签(谷歌开发者工具或是Firebug,根据你使用的浏览器而定);第三种方式是直接输出到HTML页面里并被浏览器呈现,通过document.write('My text here')。可以选择你喜欢的方式来调试。 console.log方法能接收多个参数,除了console.log("num: " + num)还可以写成console.log("num: ", num)。 稍后我们会讨论函数和对象。 变量作用域 作用域指在编写的算法函数中,我们能访问的变量(在使用时,函数作用域也可以是一个函数)。有本地变量和全局变量两种。 让我们看一个例子: var myVaribale = 'global'; myOtherVaribale = 'global'; function myFunction() { var myVariable = 'local'; return myVaribale; } function myOtherFunction() { myOtherVariable = 'local'; return myOtherVariable; } console.log(myVariable); //{1} console.log(myFunction()); //{2} console.log(myOtherVariable); //{3} console.log(myOtherFunction()); //{4} console.log(myOtherVariable); //{5} 行{1}输出global,因为它是一个全局变量。行{2}输出local,因为myVariable是在myFunction函数中声明的本地变量,所以作用域仅在myFunction内。 行{3}输出global,因为我们引用了在第二行初始化了的全局变量myOtherVariable。行{4}输出local。在myOtherFunction函数里,因为没有使用var关键字修饰,所以这里引用的是全局变量myOtherVariable并将它赋值为local。因此,行{5}会输出local(因为在myOtherFunction里修改了myOtherVariable的值)。 你可能听其他人提过在JavaScript里应该尽量少用全局变量,这是对的。通常,代码质量可以用全局变量和函数的数量来考量(数量越多越糟)。因此,尽可能避免使用全局变量。 1.2.2 操作符 编程语言里都需要操作符。在JavaScript里有算数操作符、赋值操作符、比较操作符、逻辑操作符、位操作符、一元操作符和其他操作符。我们来看一下这些操作符: var num = 0; //{1} num = num + 2; num = num * 3; num = num / 2; num++; num--; num += 1; //{2} num -= 2; num *= 3; num /= 2; num %= 3; console.log('num == 1 : ' + (num == 1)); // {3} console.log('num === 1 : ' + (num === 1)); console.log('num != 1 : ' + (num != 1)); console.log('num > 1 : ' + (num > 1)); console.log('num < 1 : ' + (num < 1)); console.log('num >= 1 : ' + (num >= 1)); console.log('num <= 1 : ' + (num <= 1)); console.log('true && false : ' + (true && false)); // {4} console.log('true || false : ' + (true || false)); console.log('!true : ' + (!true)); 在行{1},我们用了算数操作符。在下面的表格里,列出了这些操作符及其描述。 算数操作符 描 述 + 加法 - 减法 * 乘法 / 除法 % 取余 ++ 递增 -- 递减 在行{2},我们使用了赋值操作符,在下面的表格里,列出了赋值操作符及其描述。 赋值操作符 描 述 = 赋值 += 加/赋值 (x += y) == (x = x + y) -= 减/赋值 (x -= y) == (x = x - y) *= 乘/赋值 (x *= y) == (x = x * y) /= 除/赋值 (x /= y) == (x = x / y) %= 取余/赋值 (x %= y) == (x = x % y) 在行{3},我们使用了比较操作符。在下面的表格里,列出了比较操作符及其描述。 比较操作符 描 述 == 相等 === 全等 != 不等 > 大于 >= 大于等于 < 小于 <= 小于等于 在行{4},我们使用了逻辑操作符。在下面的表格里,列出了逻辑操作符及其描述。 逻辑操作符 描 述 && 与 | | 或 ! 非 JavaScript也支持位操作符,如下所示: console.log('5 & 1:', (5 & 1)); console.log('5 | 1:', (5 | 1)); console.log('~ 5:', (~5)); console.log('5 ^ 1:', (5 ^ 1)); console.log('5 << 1:', (5 << 1)); console.log('5 >> 1:', (5 >> 1)); 下面的表格对位操作符做了更详细的描述。 位操作符 描 述 & 与 | 或 ~ 非 ^ 异或 << 左移 >> 右移 typeof操作符可以返回变量或表达式的类型。我们看下面的代码: console.log('typeof num:', typeof num); console.log('typeof Packt:', typeof 'Packt'); console.log('typeof true:', typeof true); console.log('typeof [1,2,3]:', typeof [1,2,3]); console.log('typeof {name:John}:', typeof {name:'John'}); 输出如下: typeof num: number typeof Packt: string typeof true: boolean typeof [1,2,3]: object typeof {name:John}: object JavaScript还支持delete操作符,可以删除对象里的属性: var myObj = {name: 'John', age: 21}; delete myObj.age; console.log(myObj); // 输出对象{name: "John"} 这些操作符在后面的算法学习中都会用到。 1.2.3 真值和假值 在JavaScript中,true和false有些复杂。在大多数编程语言中,布尔值true和false仅仅表示true/false。在JavaScript中,如"Packt"这样的字符串值,也可以看作true。 下面的表格能帮助我们更好地理解true和false在JavaScript中是如何转换的。 数值类型 转换成布尔值 undefined false null false 布尔值 true是true,false是false 数字 +0、0和NaN都是false,其他都是true 字符串 如果字符串是空的(长度是0)就是false,其他都是true 对象 true 我们来看一些代码,用输出来验证上面的总结: function testTruthy(val){ return val ? console.log('truthy') : console.log('falsy'); } testTruthy(true); //true testTruthy(false); //false testTruthy(new Boolean(false)); //true (对象始终为true) testTruthy(''); //false testTruthy('Packt'); //true testTruthy(new String('')); //true (对象始终为true) testTruthy(1); //true testTruthy(-1); //true testTruthy(NaN); //false testTruthy(new Number(NaN)); //true (对象始终为true) testTruthy({}); //true (对象始终为true) var obj = {name:'John'}; testTruthy(obj); //true testTruthy(obj.name); //true testTruthy(obj.age); //false (年龄不存在) 1.2.4 相等操作符(==和===) 当使用这两个相等操作符时,可能会引起一些困惑。 使用==时,不同类型的值也可以被看作相等。这样的结果可能会使那些资深的JavaScript开发者都感到困惑。我们用下面的表格给大家分析一下不同类型的值用相等操作符比较后的结果。 类型(x) 类型(y) 结 果 null undefined true undefined null true 数字 字符串 x == toNumber(y) 字符串 数字 toNumber(x) == y 布尔值 任何类型 toNumber(x) == y 任何类型 布尔值 x == toNumber(y) 字符串或数字 对象 x == toPrimitive(y) 对象 字符串或数字 toPrimitive(x) == y 如果x和y是相同类型,JavaScript会比较它们的值或对象值。其他没有列在这个表格中的情况都会返回false。 toNumber和toPrimitive方法是内部的,并根据以下表格对其进行估值。 toNumber方法对不同类型返回的结果如下: 值类型 结 果 undefined NaN null 0 布尔值 如果是true,返回1;如果是false,返回0 数字 数字对应的值 字符串 将字符串解析成数字。如果字符串中包含字母,返回NaN;如果是由数字字符组成的,转换成数字 对象 Number(toPrimitive(vale)) toPrimitive方法对不同类型返回的结果如下: 值类型 结 果 对象 如果对象的valueOf方法的结果是原始值,返回原始值;如果对象的toString方法返回原始值,就返回这个值;其他情况都返回一个错误 用例子来验证一下表格中的结果。首先,我们知道下面的代码输出 true(字符串长度大于1): console.log('packt' ? true : false); 那么这行代码的结果呢? console.log('packt' == true); 输出是false,为什么会这样呢? (1) 首先,布尔值会被toNumber方法转成数字,因此得到packt == 1。 (2) 其次,用toNumber转换字符串值。因为字符串包含有字母,所以会被转成NaN,表达式就变成了NaN == 1,结果就是false。 那么这行代码的结果呢? console.log('packt' == false); 输出也是false。步骤如下所示。 (1) 首先,布尔值会被toNumber方法转成数字,因此得到packt == 0。 (2) 其次,用toNumber转换字符串值。因为字符串包含有字母,所以会被转成NaN,表达式就变成了NaN == 0,结果就是false。 那么===操作符呢?简单多了。如果比较的两个值类型不同,比较的结果就是false。如果比较的两个值类型相同,结果会根据下表判断。 类型(x) 值 结 果 数字 x和y数值相同(但不是NaN) true 字符串 x和y是相同的字符 true 布尔值 x和y都是true或false true 对象 x和y引用同一个对象 true 如果x和y类型不同,结果就是false。 我们来看一些例子: console.log('packt' === true); //false console.log('packt' === 'packt'); //true var person1 = {name:'John'}; var person2 = {name:'John'}; console.log(person1 === person2); //false,不同的对象