第 1 章 JavaScript简介

第 1 章 JavaScript简介

JavaScript是一门非常强大的编程语言。它是最流行的编程语言之一,也是互联网上最卓越的语言之一。在GitHub(世界上最大的代码托管站点)上,托管了400 000多个JavaScript代码仓库(用JavaScript开发的项目数量也是最多的,参看http://githut.info)。使用JavaScript的项目数量还在逐年增长。

JavaScript不仅可用于前端开发,也适用于后端开发,而Node.js就是其背后的技术。Node包的数量也呈指数级增长。JavaScript同样可以用于移动开发领域,并且是Apache Cordova中最流行的语言之一。Apache Cordova是一个能让开发者使用HTML、CSS和JavaScript等语言的混合式框架,你可以通过它来搭建应用,并且生成供Android系统使用的APK文件和供 iOS(苹果系统)使用的IPA文件。当然,也别忘了桌面端应用开发。我们可以使用一个名为Electron的JavaScript框架来编写同时兼容Linux、Mac OS和Windows的桌面端应用。JavaScript还可以用于嵌入式设备以及物联网(IoT)设备。正如你所看到的,到处都有JavaScript的身影!

要成为一名Web开发工程师,掌握JavaScript必不可少。

本章,你会学到JavaScript的语法和一些必要的基础,这样就可以开始开发自己的数据结构和算法了。本章内容如下:

  • 环境搭建和JavaScript基础
  • 控制结构和函数
  • JavaScript面向对象编程
  • 调试工具

1.1 JavaScript数据结构与算法

在本书中,你将学习最常用的数据结构和算法。为什么用JavaScript来学习这些数据结构和算法呢?我们已经回答了这个问题。JavaScript非常受欢迎,作为函数式编程语言,它非常适合用来学习数据结构和算法。通过它来学习数据结构比CJavaPython这些标准语言更简单,学习新东西也会变得很有趣。谁说数据结构和算法只为C、Java这样的语言而生?在前端开发当中,你可能也需要实现这些语言。

学习数据结构和算法十分重要。首要原因是数据结构和算法可以很高效地解决常见问题,这对你今后所写代码的质量至关重要(也包括性能;要是用了不恰当的数据结构或算法,很可能会产生性能问题)。其次,对于计算机科学,算法是最基础的概念。最后,如果你想入职最好的IT公司(如谷歌、亚马逊、微软、eBay等),数据结构和算法是面试问题的重头戏。

让我们开始学习吧!

1.2 环境搭建

相比其他语言,JavaScript的优势之一在于不用安装或配置任何复杂的环境就可以开始学习。每台计算机上都已具备所需的环境,哪怕使用者从未写过一行代码。有浏览器足矣!

为了运行书中的示例代码,建议你做好如下准备:安装Chrome或Firefox浏览器(选择一个你最喜欢的即可),选择一个喜欢的编辑器(如Visual Studio Code),以及一个Web服务器(XAMPP或其他你喜欢的,这一步是可选的)。Chrome、Firefox、VS Code和XAMPP在Windows、Linux和Mac OS上均可以使用。

1.2.1 最简单的环境搭建

浏览器是最简单的JavaScript开发环境。现代浏览器(Chrome、Firefox、Safari和Edge)都拥有一个叫作开发者工具的功能。如要使用Chrome中的开发者工具,可以点击右上角的菜单,选择More Tools | Developer Tools,如下图所示。

打开开发者工具,里面有一个Console标签页,可以在其中编写你的JavaScript代码,如下图所示(需要按下Enter键来执行源代码)。

1.2.2 使用Web服务器

你可能想要安装的第二个环境也很简单,但是需要安装一个Web服务器。如果一个HTML文件只包含简单的、不向服务器发送任何请求的JavaScript代码(Ajax调用),那么你可以右键点击它并选择在浏览器中直接打开。本书中需要编写的代码都很简单,可以通过这种方式执行。但是,安装一个Web服务器总是有好处的。

有很多开源和免费的Web服务器可供选择。如果你熟悉PHP的话,XAMPP会是不错的选择,它可用于Linux、Windows和Mac OS。

由于我们会专注于服务端和浏览器上的JavaScript,可以在Chrome上安装一个简单的Web服务器,它是一个叫作Web Server for Chrome的扩展。安装好之后,可以在浏览器地址栏中输入chrome://apps来找到它。

打开Web Server扩展后,可以点击CHOOSE FOLDER来选择需要在哪个文件夹中开启服务器。你可以新建一个文件夹来执行要在本书中实现的代码,也可以下载本书的源代码并将其解压缩到你喜欢的目录下,然后就能通过设定的URL(默认是http://127.0.0.1:8887)来访问它了。

{%}

本书中的所有示例都可以通过访问http://127.0.0.1:8887/examples来执行。你会看到一个包含所有示例列表的index.html文件,如下图所示。

 执行示例代码的时候,始终牢记打开开发者工具并切换到Console标签页来查看输出结果。Web Server for Chrome扩展也是用JavaScript开发的。为了获得更好的开发体验,建议使用该扩展来执行本书的示例代码,或者安装下一节将学习到的Node.js http-server

1.2.3 Node.js http-server

第三种选择就是100%的JavaScript!搭建这个环境需要安装Node.js。首先要到http://nodejs.org/下载和安装Node.js。然后,打开终端应用(如果你用的是Windows操作系统,打开Node.js的命令行,它随Node.js一同安装了),输入如下命令。

npm install http-server -g

最好手动输入这些命令,复制粘贴可能会出错。我们也可以用管理员身份执行上述命令。对于Linux和Mac OS,使用如下命令。

sudo npm install http-server -g

这条命令会在你的机器上安装一个JavaScript服务器:http-server。要启动服务器并在终端应用上运行本书中的示例代码,请将工作路径更改至示例代码文件夹,然后输入http-server,如下图所示。

为执行示例,打开浏览器,通过http-server命令指定的端口访问。

 下载代码文件的具体步骤已经在前言中介绍过了,请翻回去看一看。本书的代码包在GitHub上的托管地址是https://github.com/PacktPublishing/Learning-Java-Script-Data-Structures-and-Algorithms-Third-Edition。其他图书或视频的代码包也可以到https://github.com/PacktPublishing/查阅。别错过!

1.3 JavaScript基础

在深入学习各种数据结构和算法前,让我们先大概了解一下JavaScript。本节教大家一些相关的基础知识,有利于学习后面各章。

首先来看在HTML中编写JavaScript的两种方式。第一种方式如下面的代码所示。创建一个HTML文件(01-HelloWorld.html),把代码写进去。在这个例子里,我们在HTML文件中声明了script标签,然后把JavaScript代码都写进这个标签。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <script>
      alert('Hello, World!');
    </script>
  </body>
</html>

 尝试使用Web Server for Chrome扩展或http-server来执行上述代码,并在浏览器中查看输出结果。

第二种方式,我们需要创建一个JavaScript文件(比如01-HelloWorld.js),在里面写入如下代码。

alert('Hello, World!');

然后,我们的HTML文件看起来如下所示。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <script src="01-HelloWorld.js"></script>
  </body>
</html>

第二个例子展示了如何将一个JavaScript文件引入HTML文件。

无论执行这两个例子中的哪个,输出都是一样的。不过第二个例子是最佳实践。

 可能你在网上的一些例子里看到过JavaScript的include语句,或者放在head标签中的JavaScript代码。作为最佳实践,我们会在关闭body标签前引入JavaScript代码。这样浏览器就会在加载脚本之前解析和显示HTML,有利于提升页面的性能。

1.3.1 变量

变量保存的数据可以在需要时设置、更新或提取。赋给变量的值都有对应的类型。JavaScript的类型有字符串布尔值函数对象,还有undefinednull,以及数组日期正则表达式

尽管JavaScript有多种变量类型,然而不同于C/C++、C#或Java,它并不是一种强类型语言。在强类型语言中,声明变量时需要指定变量的类型(例如,在Java中声明一个整型变量,要使用intnum = 1;)。在JavaScript中,我们只需要使用关键字var,而不必指定变量类型。因此,JavaScript不是强类型语言。然而,对于是否要将可选的静态类型(http://github.com/dslomv/typed-objects-es7)加入未来的JavaScript标准(ECMAScript),已经有了一些讨论以及一个处于草稿状态的标准。如果需要在写JavaScript时对变量设定类型,也可以使用TypeScript。我们会在本章稍后学习有关ECMAScript和TypeScript的内容。

下面的例子介绍如何在JavaScript里使用变量。

var num = 1; // {1}
num = 3; // {2}
var price = 1.5; // {3}
var myName = '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,如下所示。

console.log('num: ' + num);
console.log('myName: ' + myName);
console.log('trueValue: ' + trueValue);
console.log('price: ' + price);
console.log('nullVar: ' + nullVar);
console.log('und: ' + und);

console.log方法不只是接收这样的参数。除了console.log('num: ' + num),我们还可以使用console.log('num: ', num)的形式。第一种写法会将结果合并为一个字符串,而第二种写法则允许我们为其添加一个描述,并在变量为对象时将其内容以可视化的方式输出。

 书中的示例代码会使用三种方式输出JavaScript的值。第一种是alert('My text here'),将输出到浏览器的警示窗口;第二种是console.log('My text here'),将把文本输出到调试工具(谷歌开发者工具或是Firebug,根据你使用的浏览器而定)的Console标签页;第三种是通过document.write('My text here')直接输出到HTML页面里并用浏览器呈现。可以选择你喜欢的方式来调试。

稍后会讨论函数和对象。

变量作用域

作用域是指,在编写的算法函数中,我们能访问变量(在使用函数作用域时,也可以是一个函数)的地方。有局部变量和全局变量两种。

让我们看一个例子。

var myVariable = 'global';
myOtherVariable = 'global';

function myFunction() {
  var myVariable = 'local';
  return myVariable;
}

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.3.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中有两种数据类型。

  • 原始数据类型nullundefined、字符串、数、布尔值和Symbol1
  • 派生数据类型/对象:JavaScript对象,包括函数、数组和正则表达式。

1Symbol是ES6新引入的数据类型,表示独一无二的值,详见4.4.2节。——编者注

JavaScript还支持delete运算符,可以删除对象里的属性。看看下面的代码。

var myObj = {name: 'John', age: 21};
delete myObj.age;
console.log(myObj); // 输出对象{name: "John"}

这些运算符在后面的算法学习中可能会用到。

1.3.3 真值和假值

在JavaScript中,true和false有些复杂。在大多数编程语言中,布尔值truefalse仅仅表示true/false结果。在JavaScript中,如Packt这样的字符串值,也可以看作true。

下表能帮助我们更好地理解true和false在JavaScript中是如何转换的。

数值类型

转换成布尔值

undefined

false

null

false

布尔值

true是true,false是false

+0-0NaN都是false,其他都是true

字符串

如果字符串是空的(长度是0)就是false,其他都是true(长度大于等于1)

对象

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); // age (属性不存在)

1.3.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

如果xy的类型相同,JavaScript会用equals方法比较这两个值或对象。没有列在这个表格中的其他情况都会返回false

toNumbertoPrimitive方法是内部的,并根据以下表格对其进行估值。

toNumber方法对不同类型返回的结果如下。

值类型

结果

undefined

NaN

null

+0

布尔值

如果是true,返回1;如果是false,返回+0

数对应的值

toPrimitive方法对不同类型返回的结果如下。

值类型

结果

对象

如果对象的valueOf方法的结果是原始值,返回原始值;如果对象的toString方法返回原始值,就返回这个值;其他情况都返回一个错误

用例子来验证一下表格中的结果。首先,我们知道下面的代码输出true(字符串长度大于1)。

console.log('packt' ? true : false);

那么下面这行代码的结果呢?

console.log('packt' == true);

输出是false,为什么会这样呢?

  • 首先,布尔值会被toNumber方法转成数,因此得到packt == 1
  • 其次,用toNumber转换字符串值。因为字符串包含字母,所以会被转成NaN,表达式就变成了NaN == 1,结果就是false

那么下面这行代码的结果呢?

console.log('packt' == false);

输出也是false,为什么呢?

  • 首先,布尔值会被toNumber方法转成数,因此得到packt == 0
  • 其次,用toNumber转换字符串值。因为字符串包含字母,所以会被转成NaN,表达式就变成了NaN == 0,结果就是false

那么===运算符呢?简单多了。如果比较的两个值类型不同,比较的结果就是false。如果比较的两个值类型相同,结果会根据下表判断。

类型(x

结果

xy的值相同(但不是NaN

true

字符串

xy是相同的字符

true

布尔值

xy都是truefalse

true

对象

xy引用同一个对象

true

如果xy类型不同,结果就是false。我们来看一些例子。

console.log('packt' === true); // false
console.log('packt' === 'packt'); // true
var person1 = {name:'John'};
var person2 = {name:'John'};
console.log(person1 === person2); // false,不同的对象

1.4 控制结构

JavaScript的控制结构与C和Java里的类似。条件语句支持if...elseswitch。循环支持whiledo...whilefor

1.4.1 条件语句

首先看一下如何构造if...else条件语句。有几种方式。

如果想让一个脚本在仅当条件(表达式)是true时执行,可以使用if语句,如下所示。

var num = 1;
if (num === 1) {
  console.log('num等于 1');
}

如果想在条件为true的时候执行脚本A,在条件为falseelse)的时候执行脚本B,可以使用if...else语句,如下所示。

var num = 0;
if (num === 1) {
  console.log('num等于 1');
} else {
  console.log('num不等于 1, num的值是 ' + num);
}

if...else语句也可以用三元运算符替换,例如下面的if...else语句。

if (num === 1) {
  num--;
} else {
  num++;
}

可以用三元运算符替换为:

(num === 1) ? num-- : num++;

如果我们有多个脚本,可以多次使用if...else,根据不同的条件执行不同的语句。

var month = 5;
if (month === 1) {
  console.log('一月');
} else if (month === 2) {
  console.log('二月');
} else if (month === 3) {
  console.log('三月');
} else {
  console.log('月份不是一月、二月或三月');
}

最后,还有switch语句。如果要判断的条件和上面的一样(但要和不同的值进行比较),可以使用swtich语句。

var month = 5;
switch (month) {
  case 1:
    console.log('January');
    break;
  case 2:
    console.log('February');
    break;
  case 3:
    console.log('March');
    break;
  default:
    console.log('Month is not January, February or March');
}

对于switch语句来说,casebreak关键字的用法很重要。case判断当前switch的值是否和case分支语句的值相等。break会中止switch语句的执行。没有break会导致执行完当前的case后,继续执行下一个case,直到遇到breakswitch执行结束。最后,还有default关键字,在表达式不匹配前面任何一种情形的时候,就执行default中的代码(如果有对应的,就不会执行)。

1.4.2 循环

在处理数组元素时会经常用到循环(数组是第3章的主要内容)。在我们的算法中也会经常用到for循环。

JavaScript中的for循环与C和Java中的一样。循环的计数值通常是一个数,然后和另一个值比较(如果条件成立就会执行for循环中的代码),之后这个数值会递增或递减。

在下面的代码里,我们用了一个for循环。当i小于10时,会在控制台中输出其值。i的初始值是0,因此这段代码会输出0到9。

for (var i = 0; i < 10; i++) {
  console.log(i);
}

我们要关注的下一种循环是while循环。当while的条件判断成立时,会执行循环内的代码。下面的代码里,有一个初始值为0的变量i,我们希望在i小于10(即小于等于9)时输出它的值。输出会是0到9。

var i = 0;
while (i < 10) {
  console.log(i);
  i++;
}

do...while循环和while循环很相似。区别是:在while循环里,先进行条件判断再执行循环体中的代码,而在do...while循环里,是先执行循环体中的代码再判断循环条件。do...while循环至少会让循环体中的代码执行一次。下面的代码同样会输出0到9。

var i = 0;
do {
  console.log(i);
  i++;
} while (i < 10);

1.5 函数

在用JavaScript编程时,函数很重要。我们的例子里也用到了函数。

下面的代码展示了函数的基本语法。它没有用到参数或return语句。

function sayHello() {
  console.log('Hello!');
}

要执行这段代码,只需要使用下面的语句。

sayHello();

我们也可以传递参数给函数。参数是会被函数使用的变量。下面的代码展示了如何在函数中使用参数。

function output(text) {
  console.log(text);
}

我们可以通过以下代码使用该函数。

output('Hello!');

你可以传递任意数量的参数,如下所示。

output('Hello!', 'Other text');

在这个例子中,函数只使用了传入的第一个参数,第二个参数被忽略。

函数也可以返回一个值,例如:

function sum(num1, num2) {
  return num1 + num2;
}

这个函数计算了给定两个数之和,并返回结果。我们可以这样使用:

var result = sum(1, 2);
output(result); // 输出3

1.6 JavaScript面向对象编程

JavaScript里的对象就是普通名值对的集合。创建一个普通对象有两种方式。第一种方式是:

var obj = new Object();

第二种方式是:

var obj = {};

我们也可以这样创建一个完整的对象:

obj = {
  name: {
    first: 'Gandalf',
    last: 'the Grey'
  },
  address: 'Middle Earth'
};

可以看到,声明JavaScript对象时,键值对中的键就是对象的属性,值就是对应属性的值。在本书中,我们创建的所有的类,如StackSetLinkedListDictionaryTreeGraph等,都是JavaScript对象。

面向对象编程(OOP)中,对象是类的实例。一个类定义了对象的特征。我们会创建很多类来表示算法和数据结构。例如我们如下声明一个类(构造函数)来表示书。

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); // 输出书名
book.title = 'new title'; // 修改书名
console.log(book.title); // 输出新的书名

类可以包含函数(通常也称为方法)。可以声明和使用函数/方法,如下所示。

Book.prototype.printTitle = function() {
  console.log(this.title);
};

book.printTitle();

我们也可以直接在类的定义里声明函数。

function Book(title, pages, isbn) {
  this.title = title;
  this.pages = pages;
  this.isbn = isbn;
  this.printIsbn = function() {
    console.log(this.isbn);
  };
}
book.printIsbn();

 在prototype的例子里,printTitle函数只会创建一次,在所有实例中共享。如果在类的定义里声明,就像前面的例子一样,则每个实例都会创建自己的函数副本。使用prototype方法可以节约内存和降低实例化的开销。不过prototype方法只能声明public函数和属性,而类定义可以声明只在类的内部访问的private函数和属性。ECMAScript 2015(ES6)引入了一套既像类定义又基于原型的简化语法。稍后我们会进一步讨论。

1.7 调试工具

除了学会如何用JavaScript编程外,还需要了解如何调试代码。调试对于找到代码中的错误十分有帮助,也能让你低速执行代码,从而看到所有发生的事情(方法被调用的栈、变量赋值等)。极力推荐你花一些时间学习一下如何调试书中的源代码,查看算法的每一步(这样也会让你对算法有深刻的理解)。

Firefox、Safari、Edge和Chrome都支持调试。有一个了解谷歌开发者工具的好教程,地址是https://developer.chrome.com/devtools/docs/javascript-debugging

除了你喜好的编辑器外,这里推荐其他几个工具,可以提升编写JavaScript的效率。

  • WebStorm:这是一个很强大的IDE,支持最新的Web技术和框架。它不是免费的,但你可以下载一个30天试用版本体验一下。
  • Sublime Text:这是一个轻量级的文本编辑器,可以自定义插件。你可以购买它的许可证来支持这个工具的开发,也可以免费使用(试用版不会过期)。
  • Atom:这也是一个轻量级的文本编辑器,由GitHub创建。它为JavaScript提供了很好的支持,也可以自定义插件。
  • Visual Studio Code:这是一个免费、开源的代码编辑器,由微软使用TypeScript开发。它使用IntelliSense提供JavaScript代码自动补全功能,并在编辑器内直接提供了内置的调试功能。同样可以自定义其插件。

上述所有编辑器都同时支持Windows、Linux和Mac OS。

使用VSCode进行调试

要直接在VSCode中调试JavaScript或ECMAScript代码,首先需要安装Debugger for Chrome扩展。

然后,启动Web Server for Chrome扩展,并在浏览器中打开链接来查看本书的示例代码(默认的URL是http://127.0.0.1:8887/examples)。

下图展示了如何直接在编辑器中进行调试。

(1) 在编辑器中,打开想要调试的JavaScript文件,将鼠标指针移至行号附近,点击添加一个断点(如图中的1所示)。调试器将在这里停止,然后可以对代码进行分析。

(2) 当Web Server启动并运行之后,点击Debug界面(如图中的2所示),选择Chrome(如图中的3所示),并点击运行图标来初始化调试进程。

(3) Chrome将自动启动。导航至我们需要调试代码的示例。一旦调试器搜索到添加了断点的那行代码,进程将停止,编辑器将获取焦点。

(4) 我们可以使用顶部工具栏来控制代码的调试方式(如图中的4所示)。可以选择继续执行,进入方法的调用,跳至下一行代码,以及重新执行或停止执行。这和在Chrome等浏览器中使用调试工具是一样的。

(5) 使用内置调试功能的好处是,我们可以在编辑器中做所有的事情(编写代码、调试和测试)。我们也可以在其中查看声明的变量和调用栈,可以监听变量和表达式(如图中的5所示),可以将鼠标指针悬停在变量上以查看它当前的值(如图中的6所示),还可以查看控制台的输出(如图中的7所示)。

本书的源代码是使用Visual Studio Code开发的,也包含了启动项的配置,所以你可以直接在VSCode中调试和测试代码(所有的细节都包含在.vscode/launch.json文件中)。运行本书源代码时推荐使用的扩展也列在了.vscode/extensions.json文件中。

1.8 小结

本章主要讲述了如何搭建开发环境,有了这个环境就可以编写和运行书中的示例代码。

本章也讲了JavaScript语言的基础知识,这些知识会在接下来的数据结构和算法学习过程中用到。

下一章,我们将学习2015年以来JavaScript中新增的功能,以及如何借助TypeScript来利用静态类型和错误检查。

目录

  • 版权声明
  • 致谢
  • 前言
  • 第 1 章 JavaScript简介
  • 第 2 章 ECMAScript和TypeScript概述
  • 第 3 章 数组
  • 第 4 章 栈
  • 第 5 章 队列和双端队列
  • 第 6 章 链表
  • 第 7 章 集合
  • 第 8 章 字典和散列表
  • 第 9 章 递归
  • 第 10 章 树
  • 第 11 章 二叉堆和堆排序
  • 第 12 章 图
  • 第 13 章 排序和搜索算法
  • 第 14 章 算法设计与技巧
  • 第 15 章 算法复杂度