第一章 快速了解JavaScript
作者: Loiane Groner
JavaScript是一门强大的语言。它是世界上最流行和最前沿的编程语言之一。比如说,在gitHub上有超过400,000个JavaScript仓库。而JavaScript的项目每年也在增长。
JavaScript不仅仅可以用在前端领域,还可以用在后端领域(Node.js就是用于后端的)。而NPM的使用者,也在迅猛增长。
如果你以后想成为一个网页开发者,JavaScript是简历上的必填项目。
在本书中,你将学习最常用的数据结构和算法。但是为什么要用JavaScript来实现数据结构和算法呢?其实我们已经回答了这个问题。JavaScript是一门流行的语言,一门值得学习的程序化语言。而且,学习新事物也是非常有趣的,并且,用JavaScript来实现数据结构和算法也比用标准的程序语言如C和Java要容易。
学习数据结构和算法是非常终于的。首先,有了数据结构和算法可以更有效的解决场见的问题。这会使你写的代码的质量更好。第二,数据结构和算法是大学里计算机专业的必修课程。第三,如果你想在大型的IT公司找到工作,数据结构和算法也是面试的主题之一。
搭建环境
与其他语言相比,JavaScript的优点在于我们不用去安装和配置一个环境。每个电脑已经有了JavaScript的环境了,即便很多电脑用户从来没有写过一行源代码。我们所需要的,就是一个浏览器!
本书推荐的是Googlr Chrome浏览器或者火狐浏览器(用你喜欢的即可),再使用一个你喜欢的编辑器(比如Sublime Text),和一个网页服务器(XAMPP 或者其他——当然这一步是可选的)。
如果你用的是火狐,推荐你用加了Firebug版的火狐浏览器(getfirebug.com)
现在,我们开始搭建环境。
一个浏览器即可
我们可以使用的,最简单的环境,就是浏览器。
你可使用火狐+Firbug。当你安装了Firebug后,你会在右上角看到这个图标:
当你打开Firebug,你会看到Console面板,然后就可以在命令行写代码了:
你也可以调整Firebug的大小。
你也可以使用谷歌浏览器。谷歌浏览器已经安装了开发者工具。按照下图,即可打开它了:
之后,你就可以在Console面板写JavaScript代码了:
使用web服务器(XAMPP)
第二个要安装的环境也很简单,但会比浏览器复杂一点。
你需要安装XAMPP,或者其他服务器。之后,在XAMPP文件夹中,你将会发现htdocs目录。你可以在这里一个放置本书代码但文件夹,或者把源代码下载在这里,如下:
之后,你可以使用本地URL(在启动XAMPP服务器后)来访问你的源代码,如下的截图所示:
Tip 执行代码时,要记的打开谷歌开发者工具或者Firebug来查看执行结果。
这就是JavaScript的全部了(Node.js)
第三个要安装的环境就是Node.js了。不像XAMPP是Apache服务器,Node.js是JavaScript服务器。
为此,我们要安装Node.js。大家可以去 nodejs.org 下载并安装Node.js。之后,打开终端并运行以下代码:
npm install http-server -g
对于Linux和Mac系统,可以使用以下代码:
sudo npm install http-server -g
这行代码会安装JavaScript的http服务器。如果想启动服务器,运行示例代码,可以在终端中把文件夹转换为有源代码的文件夹,并在终端中输入http-server ,如下图所示:
之后,打开浏览器,并在地址栏输入http-server指定的终端号码即可:
JavaScript基础
在我们深入数据结构与算法之前,我们要快速了解JavaScript语言的特点特性。这一章会详细讲到JavaScript的语言特性。
我们可以在HTML页面中,通过不同的方式来运行JavaScript代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
alert('Hello World !');
</script>
</body>
</html>
第一种运行JavaScript代码的方式,是在HTML文件中声明一个scrip标签,并把JavaScript代码写在这里。
第二种方法是,生成一个JavaScript文件,在文件内部输入以下代码:
alert('Hello World !');
之后,我们的代码是这样的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="01-HelloWorld.js">
</script>
</body>
</html>
第二种方法用于在HTML内部引用JavaScript代码。
这两种方法的运行结果是一样的。但是,第二种是比较推荐的最佳实践。
变量
变量是用于存储数据、更改数据和获取数据的。在JavaScript中,可用的数据类型有 numbers、strings、Booleans、functions和Objects。此外,还有arrays*,dates,和regular expression。以下为在JavaScript中使用变量的示例:
var num =1; //{1}
num = 3; //{2}
var price = 1.5; //{3}
var name = Packt; //{4}
var trueValue = true; //{5}
var nullValue = null; //{6}
var und; //{7}
在行 {1},我们用JavaScript声明了一个变量。虽然可以不使用var关键字,但在声明变量时使用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 );
之后,我们就可以在控制台查看这些代码的输出结果了。
变量作用域
在行 {1}会输出global,因为我们访问的是global变量。在行 {2}会输出local,因为我们在myFunction内部声明了一个local变量,所以其作用域只在myFunction函数里面。
在行 {3}会输出global,因为我们访问的是名为myOtherVarible的全局变量。在行 {4}会输出local。在myOtherFunction函数内部,因为没有使用var关键字,我们访问的是全局变量myOtherVaribale变量,并将其值指向为local。因此,在行 {5}会输出local(因为我们在myOtherFunction内部改变了myOtherVariable的值)。
你可能听说,在JavaScript中全局变量是邪恶的。是的,一般实践中是不推荐使用全局变量的。
操作符
在编程时,我们需要操作符来达成特定操作。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}展示的,是赋值操作符。下图为赋值操作符的说明:
| 赋值操作符 | 功能 |
|---|---|
| = | 赋值 |
| += | 加法赋值 |
| -= | 减法赋值 |
| *= | 乘法赋值 |
| /= | 除法赋值 |
| %= | 余数赋值 |
行 {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]: boolean
typeof {name:'John'}: object
JavaScript也提供delete操作符。delete用来删除对象的属性:
var myObj = { name:'John', age: 21 };
delete myObj.age;
console.log(myObj); //输出结果为 Object {name:'John'}
在本书,我们会使用其中的一些操作符
真值与假值
在JavaScript中,真与假的问题有一点复杂。在大多数语言中,Boolean类型会代表真与假。而在JavaScript中,像'Packt '这样的字符串也可以代表真。
下图可以帮助我们更好的理解JavaScript中的真与假:
| 值类型 | 结果 |
|---|---|
| undefined | false |
| null | false |
| Boolean | true为true,false为false |
| Number | 如果数字为+0、-0或NaN,为false;其他都为true |
| String | 如果字符串为空(长度为0),为false;字符不为空时,为ture |
| Object | 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(因为age不存在)
相等操作符( == 和 ===)
这两个相等操作符在使用过程中会产生一些混乱。
当使用 == 时,即使两个变量类型不同,也可以将两个值相等当变量视为相等。我们一依据下图分析一下 == 操作符:
| Type(x) | Type(y) | result |
|---|---|---|
| null | undefined | true |
| undefined | null | true |
| Number | String | x == toNumber(y) |
| String | Number | toNumber(x) == y |
| Boolean | Any | toNumber(x) == y |
| Any | Boolean | x == toNumber(y) |
| String or Number | Object | x == toPrimitive(y) |
| Object | String or Number | toPrimitive(x) == y |
如果x和y类型相同,JavaScript会使用equal方法来比较两个值或者对象。没有出现在此表中的类型组合,其返回结果都为false。
以下为toNumber方法的输出结果表:
| 值类型 | 结果 |
|---|---|
| undefined | NaN。 |
| null | +0。 |
| Boolean | 如果值为true,结果为1;如果值为false,结果为+0。 |
| Number | 返回数字本身。 |
| String | 它会将字符串解析为一个数字。如果这个字符串是由字母文字组成的,其结果为NaN;如果字符串是由数字组成的,其结果为数字。 |
| Object | toNumber(toPrimitive(value))。 |
以下为toPrimitive方法:
| 值类型 | 结果 |
|---|---|
| Object | 如果valueOf函数返回的是原始值,那么toPrimitive返回的是原始值;如果toString函数返回的是原始值,那么toPrimitive返回的是原始值;否则,返回的是error |
让我们拿一些例子验证一下。我们知道如果字符串长度大于1时,其真值为真:
console.log('packt' ? true: false );
那么,以下代码的结果呢?
console.log('packt' == true );
这段代码的结果是错误的!让我们来理解一下为什么:
1.首先,Boolean值被toNumber方法转化为1. 2.之后,字符串被toNumber方法转化。因为这个字符串是由字母组成的,所以它会被转化为NaN。所以会有NaN == 1,即为假。
再看看这段代码:
console.log('packt' == false );
其输出结果也为假。以下为机器解释过程: 1.首先,Boolean值被toNumber方法转化为0. 2.之后,字符串被toNumber方法转化。因为这个字符串是由字母组成的,所以它会被转化为NaN。所以会有NaN == 0,即为假。
那使用‘===’操作符会如何呢?那就会简单多了。如果我们用‘===’来比较两个值,其结果一定为false。如果两个值的类型相等,则按照以下规则进行比较:
| Type(x) | 值 | 结果 |
|---|---|---|
| Number | x和y的值一样(NaN除外) | true |
| String | x和y的字符串一样 | true |
| Boolean | x和y的布尔值一样 | true |
| Object | x和y指向的是同一个对象 | true |
如果x和y的类型不一样,其返回值为false。
让我们看看一些例子:
console.log('packt' === true); // false
conosle.log('packt' === 'packt'); // true
var person1 = {name:'John'};
var person2 = {name:'John'};
console.log(person1 === person2); //false,因为它们是两个不同的对象
控制结构
JavaScript的控制结构和C、Java的控制结构非常像。条件式述句有if...else 和 switch语句。循环有 while、do...while和for语句。
条件式述句
首先,我们看看条件式述句的if...else语句。
如果我们想让机器在条件成立时执行特定程序,我们可以使用if语句
var num = 1;
if (num ===1 ){
console.log("num is equal to 1");
}
如果我们想在条件成立时执行子程序A,条件不成立时执行子程序B,我们可以使用if...else语句:
var num =0;
if (num ===1 ){
console.log("num is equal to 1");
}else{
console.log("num is notequal to 1,the value of num is "+ num);
}
if...else语句可以被转为三元表达式:
if (num === 1){
num++;
}else{
num--;
}
可以被转化为:
(num === 1) ? num++: num--;
如果我们要判断的条件是非二元的,我们可以使用else if:
var month = 5 ;
if (month ===1 ){
console.log("January");
} else if (month === 2){
console.log("February");
} else if (month === 3){
console.log("March");
} else (month === 2){
console.log("Month is not January,February or March");
}
最后,我们还可以使用switch语句。把刚刚的代码用switch语句执行,则如下:
var month = 5;
switch(month) {
case 1:
console.log("January");
break;
case 2:
console.log("February");
break;
case 3:
console.log("March");
break;
defalut:
console.log("Month is not January,February or March");
}
循环
当我们在处理数组(当然了,数组是下一章的主题)时,会经常用到循环。要特别强调的是,在我们的算法中会经常用到for循环。
JavaScript中的for循环和C、Java的for循环是一样的。for循环由起点、终点和步数组成。
在下面的例子中,我们会使用一个for循环。当i小于10时,它会在控制台输出i的值;i被初始化为0,所以以下代码的输出结果为0到9:
for (var i= 0; i< 10; i++){
console.log(i);
}
下一个是while循环。只要while的条件为真,机器就会一直执行while里面的语句。在下面的代码中,我们有一个被初始化为0的i,之后我们会在i小于10时输出i。其输出为0到9。
var i = 0;
while( i < 10 ){
console.log(i);
i++;
}
而do...while循环与while循环非常相似。唯一的不同是,do...while循环是先执行语句,后判断条件;而while循环是先判断,再执行语句。do...while循环可以保证循环至少被执行了一次。以下为一个输出为0到9到do...while循环:
var i = 0;
do {
console.log(i);
i++;
} while(i<10)
函数
在使用JavaScript时,函数是非常重要的。在本书后面到例子中,我们也会使用很多函数。
下面到代码展示了函数到基本语法。这个函数并没有参数或者返回值:
function sayHello(){
console.log('Hello !');
使用这样的语句,就可以调用这个函数了:
sayHello();
我们也可以给函数传递参数。参数就是函数用来处理特定任务的变量。下面的代码展示了如何在函数内使用参数:
function output(text){
console.log(text);
}
通过下面的代码就可以调用这个函数了:
output('Hello','Other text');
在这个例子中,只有第一个参数被使用了,第二个参数被忽略了。
函数也可以返回一个值,如下:
funciton sum(num1,num2){
return num1 + num2;
}
这个函数会计算两个参数的和。让我们测试一下:
var result = sum(1,2);
output(result);
面向对象编程
JavaScript的对象是一系列的键值对。有两种办法可以生成JavaScript的对象。第一种方法如下:
var obj = new Object();
第二种方法如下:
var obj = {};
我们也可这样这样生成一个完整的对象:
var obj = {
name:{
first:"Gandalf",
last:"the Grey"
},
address:"Middle Earth"
};
在面向对象编程中,对象是一个类的实例。类负责定义对象的特征。届时,我们会使用类来表示一些数据结构或者算法。这是我们生成一个名为书的类的代码:
function Book(title,pages,isbn){
this.title = title;
this.pages = pages;
this.isbn = isbn;
}
为了将这个类实例化,我们可以执行以下代码:
var book = new Book('title','pag','isbn');
之后,我们可以获取并更新其特征:
console.log(book.title); //输出为title
book.title = 'new title'; //更新其值为 new title
console.log(book.title); //输出为new title
一个类还可以有函数。我们可以按照下面的方法声明并使用函数:
Book.prototype.printTitle = function(){
conosle.log(book.title);
}
book.printTitle();
我们也可以在类内部定义函数:
function Book(title,pages,isbn){
this.title = title;
this.pages = pages;
this.isbn = isbn;
this.printIsbn = function(){
conosle.log(book.isbn);
}
}
book.printIsbn();
Note 在使用了prototype的例子中,该类的所有实例都会有prinTitle方法,并且之后产生一份该方法的拷贝。而在类内部定义,即刚刚的例子中,该类的每个实例都会复制一份该函数。使用prototype方法定义方法可以节省空间。但是,prototype方法只能用于定义公共方法和公共属性。而在类内部定义,你可以定义私有属性和方法。你将会发现本书后面很多例子使用的是类内定义的方法(因为我们想让一些属性和方法为私有属性、私有方法)。但是,无论何时,我们还是应该多使用prototype来定义类的方法。
现在,我们已经讲完了JavaScript的基本概念。有了这些知识点,我们就可以开启数据结构与算法的奇妙旅程了。
用于排除故障的工具
知道如何给代码排除故障,和知道如何使用JavaScript编程一样重要。在排除故障时,我们可以更好的找到代码的错误,还可以让代码以更慢的速度运行以帮助我们了解代码内部发生的变化(调用栈的使用情况、变量赋值情况等)。
火狐和谷歌浏览器都提供了排除故障工具。以下链接有使用Google Chrome进行排除故障的教程developer.chrome.com/devtools/do…
我们可以使用任意的编辑器来写JavaScript程序。但是以下几款编辑器可以让开发过程更加高效:
- Aptana:它是一款免费的开源集成开发环境,它支持JavaScript、CSS3和HTML5等编程语言。
- Webstorm:它是一款非常强大的有着最新技术和框架的JavaScript集成开发环境。这是一个付费软件,当然你可以下载30天试用版www.jetbrains.com/webstorm。
- Sublime Text:它是一款轻量级的编辑器,你可以通过安装各种插件来个性化配置。你可以购买一个序列号来支持专业开发团队,当然你要可以使用免费版的 。www.sublimetext.com/。
小结
在本书,我们建立了用于JavaScript编程的环境。
我们也讲述了用于实现数据结构和算法的基础知识。
在下一章,我们会学习第一个数据结构——数组。数组是被包括JavaScript在内的大多数编程语言所支持的,最基本的数据结构。
注:本文翻译自Loiane Groner的《Learning JavaScript Data Structures and Algorithm》