Node.js实战(第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-19 18:33:55

点击下载

作者:(英)亚历克斯·杨(Alex Young),(美)布拉德利·马克(Bradley Meck),(美)麦克·坎特伦(Mike Cantelon)

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Node.js实战(第2版)

Node.js实战(第2版)试读:

前言

《Node.js实战》的第1版出版之后发生了很多事情,io.js问世,治理模型也发生了翻天覆地的变化。Node的包管理器孵化出了一家成功的新公司——npm,Babel和Electron等技术也改变了Node开发。

虽然Node的核心库变化不大,但JavaScript变了,大多数开发人员都用上了ES2015的功能特性,所以我们改写了上一版中的所有代码,用上了箭头函数、常量和解构。因为Node的库和自带的工具看起来仍然和4.x之前的版本差不多,所以我们在这一版的更新中瞄准了社区。

为了体现Node开发人员在实际工作中面临的问题,本书在结构上进行了调整。Express和Connect的分量轻了,涉及的技术范围广了。书中介绍了全栈开发者所需的全部技术,包括前端构建系统、选择Web框架、在Node中与数据库的交互、编写测试和部署Web程序。

除了Web开发,本书还有编写命令行程序和Electron桌面程序的章节,让你充分利用自己的Node和JavaScript技能。

本书不仅要向你介绍Node和它的生态系统,还想尽可能让你了解那些影响Node发展的背景知识,比如一般在Node和JavaScript书籍中并不介绍的Unix哲学和如何正确、安全地使用数据库。希望这些知识能拓宽你的眼界,加深你对Node和JavaScript的理解,帮你在面临新的问题时找到解决办法。致谢

首先要感谢本书上一版的作者们,他们做出了巨大贡献:Mike Cantelon、Marc Harter、T.J. Holowaychuk和Nathan Rajlich。还要感谢Manning的团队,如果没有他们的鼓励,这一版也不会问世。感谢我的策划编辑Cynthia Kane,在更新原内容的漫长过程中让我保持专注。如果没有Doug Warren详尽的技术校对,本书及其中代码的正确率恐怕连现在的一半都不到。最后要感谢在写作及开发过程中提供反馈的评审人员:Austin King、Carl Hope、Chris Salch、Christopher Reed、Dale Francis、Hafiz Waheedud din、HarinathMallepally、Jeff Smith、Marc-Philippe Huget、Matthew Bertoni、Philippe Charrieère、Randy Kamradt、Sander Rossel、Scott Dierbeck和William Wheeler。关于本书

本书第1版重点介绍了如何用Web框架Connect和Express开发Web程序。第2版则根据Node开发的变化做了调整。我们会介绍前端构建系统、流行的Node Web框架,以及如何用Express从头开始搭建Web程序,还会讲到自动化测试和Node Web程序的部署。

因为用Node做的命令行开发者工具和用Electron做的桌面端程序越来越多,所以本书专门用了两章的篇幅分别介绍这两块内容。

本书假定你熟悉基本的编程概念。但考虑到有些开发人员还没有接触过新的JavaScript,所以第1章将会介绍JavaScript和ES2015。路线图

本书分为三部分。

第一部分介绍Node.js,讲解用它进行开发所需的基础技术。第1章介绍了JavaScript和Node的特性,通过示例代码一步步进行讲解。第2章介绍了基本的Node.js编程概念。第3章完整地演示了如何从头开始搭建一个Web程序。

第二部分重点介绍Web开发,内容最多,篇幅也最长。第4章是前端构建系统的揭秘。如果你在项目中用到过Webpack或Gulp,但并没有真正掌握它们,那么可以学习一下这一章的内容。第5章介绍了Node中最流行的服务器端框架。第6章详细介绍了Connect和Express。第7章是模板语言,它可以提升服务端代码的编写效率。大多数Web程序都需要数据库,所以第8章介绍了很多种可以用在Node中的数据库,关系型和NoSQL都有涉及。第9章和第10章讲了测试和部署,包括云端部署。

第三部分是Web程序开发之外的内容。第11章讲了如何用Node搭建命令行程序,创建出开发人员熟悉的文字界面。如果你喜欢用Node搭建像Atom一样的桌面程序,可以看看介绍Electron的第12章。

本书还有三个附录。附录A讲了如何在macOS和Windows上安装Node,附录B详细介绍了如何实现网络内容抓取,附录C介绍了Connect的官方中间件组件。编码规范及下载

书中的代码遵循通用JavaScript规范。缩进用空格,不用制表符。尽量不要让一行代码的长度超过80个字符。很多代码清单中都加了注释,指出了其中的关键概念。

每行一条语句,简单语句后面加分号。代码块放在大括号中,左括号放在代码块开始行的末尾处,右括号的缩进跟代码块开始行的缩进保持一致,在垂直方向上对齐。

书中示例的源码请至图灵社区本书主页http://www.ituring.com.cn/book/1993随书下载处下载。关于封面图片

本书封面上的画像标题为“城镇里的男人”,摘自19世纪法国出版的沙利文·马雷夏尔(Sylvain Maréchal)四卷本的地域服饰风俗摘要。其中每幅插图都是手工精心绘制并上色的。马雷夏尔这套书展示的丰富服饰,令我们强烈感受到2000年前乡村与城镇的巨大文化差异。不同地域的人山水阻隔,语言不通。无论奔走于街巷,还是驻足于乡间,通过他们的服饰,一眼就能看出他们的生活场所、职业,以及生活境况。

时过境迁,书中描绘的那些区域性服饰差异如今已经不复存在。即使是不同国家,都很难再看出人们着装的区别,再不必说城镇和乡村了。或许,我们今天多姿多彩的人生,正是从前那些文化差异的体现。只不过,如今的生活更加多元,而且技术环境下的生活节奏也更快了。

今时今日,计算机图书层出不穷,Manning就以马雷夏尔这套书中多样性的图片,来表达对IT行业日新月异的发明与创造的赞美。第一部分Node基础知识介绍

现如今,Node已经出落成了一个成熟的Web开发平台。本书第1章到第3章介绍Node的主要特性,包括如何使用npm和Node的核心模块。你还将看到如何在Node上使用现代版JavaScript,以及如何从头开始构建一个Web应用程序。看完这些章节之后,对于Node能做什么,以及该如何创建自己的项目,你将会有非常深刻的认识。第1章欢迎进入Node.js的世界本章内容● Node.js是什么● 定义Node应用程序● 使用Node的优势● 异步和非阻塞I/O

Node.js是一个JavaScript运行平台,其显著特征是它的异步和事件驱动机制,以及小巧精悍的标准库。Node目前有两个活跃版本:长期支持版(LTS)和当前版,由Node.js基金会进行管理并提供支持。这个行业联盟遵循开放式治理模型,如果想了解更多与Node管理相关的信息,可以查阅其官网上的文档。

自2009年Node.js问世以来,JavaScript渐渐变成了能开发所有软件的语言,其地位也越来越重要,不再是只能勉强在浏览器上用一下的鸡肋语言了。这里有ECMAScript 2015的功劳,因为它解决了之前那些ECMAScript标准中遗留下来的几个关键问题。Node所用的Google V8引擎就是基于ECMAScript 2015开发的。ECMAScript 2015是ECMAScript标准的第6个版本,所以有时也被称为ES6,一般简写为ES2015。Node、React和Electron等技术创新成果的功劳也不可小觑,是它们让JavaScript无处不在:从服务器到浏览器,到原生的移动端应用程序。甚至像微软这样的大公司都对JavaScript敞开了怀抱,也为Node的成功起到推波助澜的作用。

本章更深入介绍Node、Node的事件驱动非阻塞模型,以及JavaScript成为优秀的通用编程语言的一些原因。下面先介绍一个典型的Node Web应用程序。1.1 一个典型的Node Web应用程序

大体上来说,Node和JavaScript的优势之一是它们的单线程编程模型。多个线程一般会引入bug,尽管一些新的编程语言,包括Go和Rust,试图提供更加安全的并发工具,但Node仍然保留了JavaScript在浏览器中所用的模型。在为浏览器编写代码时,我们写的指令序列一次执行一条,代码不是并行执行的。然而对于用户界面来说,这样是不合理的:没有哪个用户想在浏览器执行网络访问或文件获取这样的低速操作时干等着。为了解决这个问题,浏览器引入了事件机制:在你点击按钮时,就有一个事件被触发,还有一个之前定义的函数会跑起来。这种机制可以规避一些在线程编程中经常出现的问题,比如资源死锁和竞态条件。1.1.1 非阻塞I/O

那么在服务器端编程中,这有什么意义呢?其实服务器端编程面对的情况也差不多:访问磁盘和网络这样的I/O请求会比较慢,所以我们希望,在读取文件或通过网络发送消息时,运行平台不会阻塞业务逻辑的执行。Node用三种技术来解决这个问题:事件、异步API、非阻塞I/O。在Node程序员看来,非阻塞I/O是个底层术语。它的意思是说,你的程序可以在做其他事情时发起一个请求来获取网络资源,然后当网络操作完成时,将会运行一个回调函数来处理这个操作的结果。

图1-1展示了一个典型的Node Web应用程序,它用Web应用库Express来处理商店的订单流程。为了购买产品,浏览器发起了一个请求,然后应用程序检查库存,为该用户创建一个账号,发回执邮件,并返回一个JSON HTTP响应给浏览器。同时在做的其他事情有:发送了一封回执邮件,更新了数据库来保存用户的详细信息和订单。代码本身很简单,就是JavaScript指令,但运行平台是并发操作的,因为它用了非阻塞I/O。

图1-1 一个Node应用程序中的异步非阻塞组件

在图1-1中,数据库是通过网络访问的。Node中的网络访问是非阻塞的,它用了一个名为libuv的库来访问操作系统的非阻塞网络调用。这个库在Linux、macOS和Windows中的实现是不同的,但不用担心,因为你只需要会用操作数据库的JavaScript库就可以了。只要写一些db.insert(query, err => {})这样的代码,Node就会帮你完成那些经过高度优化的非阻塞网络操作。

访问硬盘也差不多,但又不完全一样。在生成了回执邮件并从硬盘中读取邮件模板时,libuv借助线程池模拟出了一种使用非阻塞调用的假象。管理线程池是个苦差事,相较而言,email.send('template.ejs', (err, html) => {})这样的代码肯定要容易理解得多了。

在进行速度较慢的处理时让Node能做其他事情,是使用带非阻塞I/O的异步API真正的好处。即便你只有一个单线程、单进程的Node Web应用,它也可以同时处理上千个网站访客发起的连接。要想知道Node是如何做到的,得先研究一下事件轮询。1.1.2 事件轮询

我们把图1-1放大,仔细研究“响应浏览器的请求”那部分。在这个应用程序中,Node内置的HTTP服务器库,即核心模块http.Server,负责用流、事件、Node的HTTP请求解析器的组合来处理请求,它是本地代码。你用Express Web应用库添加的回调函数,也是由它触发的。这个回调函数又会触发数据库查询语句,最终应用程序会用HTTP发送JSON作为响应。整个过程用了三个非阻塞网络调用:一个用于请求,一个用于数据库,还有一个用于响应。Node是如何调配这些网络操作的呢?答案是事件轮询(event loop)。图1-2展示了如何用事件轮询完成这三个网络操作。

图1-2 事件轮询

事件轮询是单向运行的先入先出队列,它要经过几个阶段,轮询中每个迭代都要运行的重要阶段已经在图1-2中展示出来了。首先是计时器开始执行,这些计时器都是用JavaScript函数setTimeout和setInterval安排好的。接下来是运行I/O回调,即触发你的回调函数。轮询阶段会去获取新的I/O事件,最后是用setImmediate安排回调。这是一个特例,因为它允许你将回调安排在当前队列中的I/O回调完成之后立即执行。现在你可能还会觉得有点儿抽象,不过只需要记住,尽管Node是单线程的,但你仍然可以用它提供的工具写出可伸缩的高效代码。

你可能注意到了,前面几页中的代码用到了ES2015的箭头函数。Node支持很多JavaScript的新特性,所以我们想先带你看一看能用哪些新特性来写出更棒的代码,然后再继续介绍Node。1.2 ES2015、Node和V8

如果你以前曾因JavaScript没有类而伤心难过,或者被它奇怪的作用域规则搞得头晕脑胀,那你肯定会喜欢我们接下来要讲的内容。Node解决了很多问题!现在你可以创建类了!const和let(代替了var)解决了作用域的问题。从Node 6开始,你可以用默认函数参数、剩余参数、spread操作符、for...of循环、模板字符串、解构、生成器等很多新特性。http://node.green上汇总了Node支持的ES2015特性,建议你看一下。

先说类。在ES5及之前的版本中,我们要用prototype对象来创建类似于类的结构:function User() { // 构造器} User.prototype.method = function() { // 方法};

有了Node 6和ES2015,你可以用类将上面的代码写成:class User { constructor() {} method() {}}

代码少了,也更容易理解了。但还不止于此,Node也支持子类、超类和静态方法。对于熟悉其他语言的人来说,采用了类语法的Node比ES5更好用。

const和let是从Node 4开始支持的。在ES5中,所有变量都是用var创建的。不管是在函数中还是全局作用域中,都是用var定义变量,所以我们没办法在if语句、for循环以及其他块中定义块级别的变量。

我应该用const还是let在决定是用const还是用let时,几乎都可以用const。因

为你的大部分代码都是在用你自己的类实例、对象常量或不

会变的值,所以大部分情况下都可以用const。即便是有可

修改属性的对象,也是可以用const声明的,因为const的意

思是引用是只读的,而不是值是不可变的。

Node还有原生的promise和生成器。为了让我们能用流畅的接口风格编写异步代码,有很多库都支持promise。对于流畅的接口风格,你可能并不陌生,如果你用过jQuery之类的API,甚至只要用过JavaScript数组,就已经见过它是什么样的了。下面就是一个将调用链起来处理数组的小例子:[1, 2, 3] .map(n => n * 2) .filter(n => n > 3);

生成器能把异步I/O变成同步编程风格。Koa Web应用库中用到了生成器,你可以研究一下它的代码以了解生成器的用法。如果结合Koa使用promise和其他生成器,你就可以抛开层层嵌套的回调,在值上yield。

ES2015中的模板字符串在Node中也很好用。在ES5中,字符串常量不支持插值,也不能跨行。现在我们可以用反引号(`)定义模板字符串,不仅可以插值,而且还可以跨行。比如像下面这个例子一样,在Web应用中直接定义一小段HTML模板:this.body = `

Hello from Node

Welcome, ${user.name}!

`;

在ES5中,前面那个例子只能写成这样:this.body = '\n';this.body += '

\n';this.body += '

Hello from Node

\n';this.body += '

Welcome, ' + user.name + '

\n';this.body += '
\n';

老套路不仅代码多,而且还容易出错。对Node程序员来说,最后一个非常重要的特性是箭头函数。箭头函数的语法非常精炼。比如说,如果你要写有一个参数和一个返回值的回调函数,那么像下面这么简单就可以:[1, 2, 3].map(v => v * 2);

在Node中,我们一般会需要两个参数,因为回调的第一个参数通常是错误对象。这时候需要用括号把参数括起来:const fs = require('fs');fs.readFile('package.json', (err, text) => console.log('Length:', text.length));

如果函数体的代码不止一行,则需要用到大括号。箭头函数的价值不仅体现在其精炼的语法上,还跟JavaScript作用域有关。在ES5及之前版本的语言中,在函数中定义函数会把this引用变成全局对象。就因为这个问题,下面这种按ES5写的类很容易出错:function User(id) {// 构造器 this.id = id;} User.prototype.load = function() { var self = this; var query = 'SELECT * FROM users WHERE id = ?'; sql.query(query, this.id, function(err, users) { self.name = users[0].name; });};

给self.name赋值那行代码不能写成this.name,因为这个函数的this是个全局变量。常用的解决办法是在函数的入口处将this赋值给一个变量。但箭头函数的绑定没有这个问题。所以在ES2015中,上面这个例子可以改写成更加直观的形式:class User { constructor(id) { this.id = id; }  load() { const query = 'SELECT * FROM users WHERE id = ?'; sql.query(query, this.id, (err, users) => { this.name = users[0].name; });}}

你不仅可以用const更好地建模数据库查询,而且还去掉了麻烦的self变量。让Node代码变得更容易理解的ES2015的特性还有很多,篇幅所限就不一一介绍了。但我们接下来要看看这都是谁的功劳,以及它与之前讲的非阻塞I/O有什么关系。1.2.1 Node与V8

Node的动力源自V8 JavaScript引擎,是由服务于Google Chrome的Chromium项目组开发的。V8的一个值得称道的特性是它会被JavaScript直接编译为机器码,另外它还有一些代码优化特性,所以Node才能这么快。在1.1.1节,我们曾提到过Node的另一个本地部件libuv,它是负责处理I/O的。V8负责JavaScript代码的解释和执行。用C++绑定层可将libuv和V8结合起来。图1-3给出了组成Node的所有软件组件。

图1-3 Node.js的软件栈

因此,Node中能用的JavaScript特性都可以追溯到V8对该特性的支持。这一支持是通过特性组来管理的。1.2.2 使用特性组

Node包含了V8提供的ES2015特性。这些特性分为shipping、staged和in progress三组。shipping组的特性是默认开启的,staged和in progress组的特性则需要用命令行参数开启。如果你想用staged特性,可以在运行Node时加上参数--harmony,V8团队将所有接近完成的特性都放在了这一组。然而,in progress特性稳定性较差,需要具体的特性参数来开启。Node的文档建议通过grep "in progress"来查询当前可用的in progress特性:node --v8-options | grep "in progress"

在不同的Node版本中执行这条命令后得到的结果也是不同的。Node自己也有个版本计划,定义了它要提供哪些API。1.2.3 了解Node的发布计划

Node的发行版分为长期支持版(LTS)、当前版和每日构建版三组。LTS版有18个月的支持服务,期满后还有12个月的维护性支持服务。版本号是按照语义版本(SemVer)编制的。SemVer给每个版本定义了一个主要、次要和补丁版本号。比如6.9.1的主要版本号是6,次要版本号是9,补丁版本号是1。只要看到主版本号发生变化,那就意味着有些API可能不兼容了,也就是说如果要用这个版本的Node,那么你的项目需要重新测试一下。另外,按Node的发布规则,主版本号增长意味着新的当前版也已经切下来了。每日构建版的构建是自动进行的,每隔24小时一次,包含这24小时内的最新修改,但一般只用来测试Node的最新特性。

用哪个版本取决于你的项目和组织。有些人可能喜欢更新不那么频繁的LTS,对于那些难以管理频繁更新的大公司来说,这个版本可能更好。但如果你想跟上性能和功能的改进,当前版更合适。1.3 安装Node

安装Node的最简单的方法是使用其官网上的安装程序。可以用对应Mac或Windows的安装程序安装最新的当前版(写作本书时是6.5)。或者用操作系统上的包管理器,Debian、Ubuntu、Arch、Fedora、FreeBSD、Gentoo和SUSE全都有安装包,另外还有Homebrew和SmartOS的安装包。如果没有能用在你的操作系统上的包,也可以下载源码自己构建。提示 附录A提供了更加详细的Node安装指南。

Node官网(https://nodejs.org/zh-cn/download/)上有个包含所有安装包的列表,源码在GitHub(https://github.com/nodejs/node)上。建议收藏一下Node在GitHub上的项目主页以备不时之需,比如有时候你可能想看看它的源码。

装好之后,可以在终端中输入node -v来试一下。这个命令应该会输出你所安装的Node的版本号。接下来,创建一个名为hello.js的文件,内容如下所示:console.log("hello from Node");

保存文件,输入node hello.js运行它。恭喜你!都准备好了,你可以开始用Node写程序了!

在Windows、Linux和macOS上快速上手如果你刚开始接触编程,还没找到自己喜欢的文本编辑

器,那么Visual Studio Code是个不错的选择。这是微软开

发的,但开源,可以免费下载,支持Windows、Linux和

macOS。Visual Studio Code为新手提供了一些友好的辅助功

能,包括JavaScript语法高亮、Node核心模块自动补足等。

所以你的JavaScript代码看起来会更清晰,并且你在输入时

还能看到一个所支持方法和对象的列表。它还有个命令行界

面,可以输入Node来调用Node。有了这个命令行界面,需

要运行Node和npm命令时会很方便。Windows用户可能会觉

得这个比cmd.exe好用。我们的代码都在Windows上用

Visual Studio Code测试过,所以应该不需要任何特殊的东

西来运行本书中的例子。可以从参照Visual Studio Code Node.js教程开始。

Node还有一些自带的工具。它不单单是一个解释器,而是由一套工具组成的平台。接下来我们详细介绍一下这些工具。1.4 Node自带的工具

Node自带了一个包管理器,以及从文件和网络I/O到zlib压缩等无所不包的核心JavaScript模块,还有一个调试器。npm包管理器是这个基础设施中的重要组成部分,也是我们要重点介绍的。

如果你想检查一下Node是否已经安装成功,可以在命令行里运行node -v和npm -v。这两个命令分别用来显示你所安装的Node和npm的版本。1.4.1 npm

命令行工具npm是用npm调用的。你可以用它来安装npm注册中心里的包,也可以用它来查找和分享你自己的项目,开源的和闭源的都行。注册中心里的每个npm包都会有个页面显示它的自述文件、作者和下载统计信息。

另外,npm还是一家提供npm服务的公司的名字。这家公司为企业提供商业服务,包括托管私有的npm包。你可以按月支付服务费,把公司的源码托管给他们,这样你的JavaScript开发人员就可以用npm轻松安装你的私有包了。

在用npm安装这些包时,你要决定是装在你的项目中还是装在全局。要全局安装的包一般是工具,即你要在命令行里运行的程序,比如gulp-cli包。

npm要求Node项目所在的目录下有一个package.json文件。创建package.json文件的最简单方法是使用npm。在命令行中输入下面这些命令:mkdir example-projectcd example-projectnpm init -y

打开package.json,你会看到简单的JSON格式的项目描述信息。如果你现在用带有参数--save的npm命令从npm网站上安装一个包,它会自动更新你的package.json文件。试着输入npm install,或简写为npm i:npm i --save express

打开package.json,应该会看到dependencies属性下面新增加的express。另外,看一下node_modules文件夹,你会看到新创建的express目录。里面是刚安装的那个版本的Express。你也可以用 --global参数做全局安装。应尽可能地将包安装在项目里,但对于用在Node JavaScript代码之外的命令行工具,全局安装更合适。比如用npm安装命令行工具ESLint时,我们采用全局安装。

开始用Node之后,你会经常用到来自npm的包。另外,Node还自带了很多非常实用的库,统称为核心模块,接下来我们就去看一下。1.4.2 核心模块

Node的核心模块就相当于其他语言的标准库,它们是编写服务器端JavaScript所需的工具。大多数服务器端开发人员都知道,JavaScript标准本身没有任何处理网络的东西,甚至连处理文件I/O的东西都没有。Node以最少的代码给它加上了文件和TCP/IP网络功能,使其成为了一个可用的服务器端编程语言。1. 文件系统Node不仅有文件系统库(fs、path)、TCP客户端和服务端

库(net)、HTTP库(http和https)和域名解析库(dns),还有

一个经常用来写测试的断言库(assert),以及一个用来查询平

台信息的操作系统库(os)。Node还有一些独有库。事件模块是一个处理事件的小型

库,Node的大多数API都是以它为基础来做的。比如说,流模块

用事件模块提供了一个处理流数据的抽象接口。因为Node中的

所有数据流用的都是同样的API,所以你可以很轻松地组装出软

件组件。如果你有一个文件流读取器,就可以很方便地把它跟压

缩数据的zlib连接到一起,然后这个zlib再连接一个文件流写入

器,从而形成一个文件流处理管道。在下面这段代码中,我们用Node的fs模块创建了读和写流,

然后把它们通过另外一个流(gzip)连接起来传输数据,就这个

例子而言,就是压缩。代码清单1-1 使用核心模块和流

const fs = require('fs');

const zlib = require('zlib');

const gzip = zlib.createGzip();

const outStream = fs.createWriteStream('output.js.gz');

 

fs.createReadStream('./node-stream.js')

.pipe(gzip)

.pipe(outStream);2. 网络曾几何时,我们总是说创建一个简单的HTTP服务器才是

Node真正的Hello World。在Node中搭一个服务器只需要加载http

模块,然后给它一个函数。这个函数有两个参数,即请求和响应。

你可以在自己的终端中运行一下这段代码。代码清单1-2 用Node的http模块写的Hello World

const http = require('http');

const port = 8080;

 

const server = http.createServer((req, res) => {

res.end('Hello, world.');

});

 

server.listen(port, () => {

console.log('Server listening on: http://localhost:%s', port);

});将上面的代码保存到hello.js文件中,用node hello.js运行

它。访问 http://localhost:8080,你应该能看到第4行的问候信

息。Node的核心模块精炼强悍。你甚至不需要用npm安装任何

东西就可以用这些模块完成很多事情。可以参阅Node的api网站

来了解核心模块的更多相关信息。

最后一个内置工具是调试器。下一节我们将会通过一个例子来介绍它。1.4.3 调试器

Node自带的调试器支持单步执行和REPL(读取-计算-输出-循环)。这个调试器在工作时会用一个网络协议跟你的程序对话。带着debug参数运行程序,就可以对这个程序开启调试器。比如要调试代码清单1-2中的代码:node debug hello.js

然后应该能看到下面这样的输出:< Debugger listening on [::]:5858connecting to 127.0.0.1:5858 ... okbreak in node-http.js:1> 1 const http = require('http'); 2 const port = 8080; 3

Node启动了这个程序,并连到5858端口上对它进行调试。你可以输入help看一下它的命令,然后输入命令c让程序继续执行。Node启动程序时总是把它置于break状态上,所以在你想做任何事情之前,总要先让它继续执行。

我们可以在代码中的任何地方添加debugger语句来设置断点。遇到debugger语句后,调试器就会把程序停住,然后你可以输入命令。比如说,你写了一个REST API来为新用户创建账号,但发现代码貌似没有把新用户密码的散列值写到数据库里。你可以在User类的save方法那里加一个debugger,然后单步执行每一条指令,看看发生了什么。

交互式调试Node支持Chrome调试协议。如果要用Chrome的开发者

工具调试一段脚本,可以在运行程序时加上 --inspect参数:

node --inspect --debug-brk这样Node就会启动调试器,并停在第一行。它会输出

一个URL到控制台,你可以在Chrome中打开这个URL,然

后用Chrome的调试器进行调试。Chrome的调试器可以一行

行地执行代码,还能显示每个变量和对象的值。这要比在代

码里敲console.log好得多。

第9章还会详细讲解调试技术。如果你现在就想试一下,那么最好的入手点是Node调试器手册。

前面我们介绍了Node的工作机理,以及它给开发人员所提供的工具。现在你可能很想知道,人们在生产环境中用Node都做了什么样的程序。接下来我们就看看可以用Node实现的几种程序。1.5 三种主流的Node程序

Node程序主要可以分成三种类型:Web应用程序、命令行工具和后台程序、桌面程序。提供单页应用的简单程序、REST微服务以及全栈的Web应用都属于Web应用程序。你可能已经使用过用Node写的命令行工具了,比如npm、Gulp和Webpack。后台程序就是后台服务,比如PM2进程管理器。桌面程序一般是用Electron框架写的软件,Electron用Node作为基于Web的桌面应用的后台。Atom和Visual Studio Code文本编辑器都属于这一类。1.5.1 Web应用程序

因为Node是服务器端JavaScript平台,所以用它搭建Web应用程序是理所当然的事情。既然客户端和服务器端用的都是JavaScript,代码难免会有在这两种环境里重用的机会。Node Web应用一般是用Express这样的框架写的。第6章介绍了几个主要的Node服务器端框架,第7章专门介绍了Express和Connect,第8章是Web应用程序模板。

你可以通过创建一个新目录,然后在里面安装Express模板,来快速创建一个Express Web应用程序:mkdir hello_expresscd hello_expressnpm init -ynpm i express --save

接下来把下面的JavaScript代码存到server.js中。代码清单1-3 一个Node Web应用程序const express = require('express');const app = express(); app.get('/', (req, res) => { res.send('Hello World!');});app.listen(3000, () => { console.log('Express web app on localhost:3000');});

现在输入npm start,启动这个监听端口3000的Node Web服务器。在浏览器中打开http://localhost:3000,就能看到res.send那行代码发回的文本。

在前端开发的世界中,Node也在发挥着重要作用,因为它是进行语言转译的主要工具,比如从TypeScript到JavaScript。转译器将一种高级语言编译成另外一种高级语言,传统的编译器则将一种高级语言编译成一种低级语言。第4章将会专门介绍前端构建系统,到时候你会看到npm脚本、Gulp和Webpack的用法。

并不是所有的Web开发都会涉及Web应用的构建。有时候,在重建一个网站时,你需要把数据从老网站上扒出来。我们专门加了个附录B来讲网页抓取,以便展示如何用Node的JavaScript运行平台处理文档对象模型(DOM),同时也展示了如何在Express Web应用这个舒适区之外使用Node。如果你只是想快速地构建一个简单的Web应用,第3章为我们提供了一个完整的Node Web应用程序搭建教程。1.5.2 命令行工具和后台程序

Node可以用来编写命令行工具,比如JavaScript开发人员所用的进程管理器和JavaScript转译器。它也可以作为一种方便的方式来编写其他操作的命令行工具,比如图片转换、控制媒体文件播放的脚本等。

你可以试一下下面这个例子。创建一个名为cli.js的新文件,添加如下代码:const [nodePath, scriptPath, name] = process.argv;console.log('Hello', name);

用node cli.js yourName运行这个脚本,你会看到Hello yourName。这用到了ES2015的解构,它会从process.argv中拉取第三个参数。所有Node程序都可以访问process对象,这是用户向程序中传递参数的基础。

Node命令行程序还可以做其他事情。如果在程序开头的地方加上#!,并赋予其执行许可(chmod +x cli.js),shell就可以在调用程序时使用Node。也就是说可以像运行其他shell脚本那样运行Node程序。在类Unix系统中用下面这样的代码:#!/usr/bin/env node

这样你就可以用Node代替shell脚本。也就是说Node可以跟其他任何命令行工具配合,包括后台程序。Node程序可以由cron调用,也可以作为后台程序运行。

如果你觉得这一切都很陌生,不用担心。第11章将会介绍如何编写命令行工具,展示Node在这种程序上的实力。比如说,大量使用流作为通用API的命令行工具,而流处理是Node最强大的功能之一。1.5.3 桌面程序

如果你用过Atom或Visual Studio Code文本编辑器,那就用过Node。Electron框架用Node做后台,所以只要需要访问硬盘或网络,Electron就会用到Node。Electron还用Node来管理依赖项,也就是说你可以用npm往Electron项目里添加包。

如果你现在就想试一下,可以复制Electron的存储库并启动一个应用程序:git clone https://github.com/electron/electron-quick-startcd electron-quick-startnpm install && npm startcurl localhost:8081

如果你想要了解如何用Electron写程序,可以翻到第12章看一下。1.5.4 适合Node的应用程序

我们已经看过一些能用Node搭建的应用程序了,但Node擅长的领域不止于此。Node一般用来创建实时的Web应用,这几乎无所不包,从直接面对用户的聊天服务器到采集分析数据的后台程序都属于此类。在JavaScript中,函数是一等对象,Node又有内建的事件模型,所以用它来写异步实时程序比用其他脚本语言更自然。

如果你要搭建传统的模型-视图-控制器(MVC)Web应用,用Node也很适合。Ghost等一些流行的博客引擎就是用Node搭建的。在搭建这几种类型的Web应用程序方面,Node是一个经过实践检验的平台。虽然开发风格跟用PHP的WordPress不同,但Ghost支持的功能是类似的,包括模板和多用户管理区。

Node还能做一些用其他语言很难做到的事情。它是基于JavaScript的,所以在Node中能运行浏览器中的JavaScript。复杂的客户端应用可以经过改造在Node服务器上运行,让服务器进行预渲染,从而加快页面在浏览器中的渲染速度,也有利于搜索引擎进行索引。

最后,如果你想要搭建一个桌面端或移动端应用,建议试一下Electron,它也是由Node支撑起来的。现在Web用户界面的体验跟桌面端应用一样丰富,Electron桌面端应用足以抗衡本地Web应用,还能缩短开发时间。Electron支持三种主流操作系统,所以你可以在Windows、Linux和macOS上重用这些代码。1.6 总结● Node是用来搭建JavaScript应用程序的平台,有基于事件和非阻

塞的特性。● V8被用作JavaScript运行时。● libuv是提供快速、跨平台、非阻塞I/O的本地库。● 被称为核心模块的Node标准库很精巧,为JavaScript添加了磁盘

I/O。● Node自带了一个调试器和一个依赖管理器(npm)。● Node可以用于搭建Web应用程序、命令行工具,甚至桌面程

序。第2章Node编程基础本章内容● 用模块组织代码● 用回调处理一次性事件● 用事件发射器处理重复性事件● 实现串行和并行的流程控制● 使用流程控制工具

与大多数开源平台不同,Node设置起来很容易,对内存和硬盘空间要求不高,也不需要复杂的集成开发环境或构建系统。但对于刚起步的新手来说,掌握一些基础知识会有很大帮助。本章要解决摆在Node开发新手面前的两个难题:● 如何组织代码;● 如何实现异步编程。

本章会介绍几种重要的异步编程技术,让你能牢牢控制程序的执行。包括:● 如何响应一次性事件;● 如何处理重复性事件;● 如何让异步逻辑顺序执行。

不过我们要先讲一下如何用模块组织代码。模块是Node的一种代码组织和包装方式,让代码更容易重用。2.1 Node功能的组织及重用

在创建程序时,不管是用Node还是其他工具,基本不可能把所有代码都放到一个文件中。当出现这种情况时,传统的方式是按逻辑相关性对代码分组,将包含大量代码的单个文件分解成多个文件,如图2-1所示。

图2-1 与全部存放在一个长文件中的代码相比,用目录和单独的文件组织起来的代码更容易查找

在某些语言中,比如PHP和Ruby,整合另一个文件(我们称之为“included”文件)中的逻辑,可能意味着在被引入文件中执行的逻辑会影响全局作用域。也就是说,被引入文件创建的任何变量和声明的任何函数,都可能会覆盖包含它的应用程序所创建的变量和声明的函数。

假设用PHP写程序,可能会有下面这种逻辑:function uppercase_trim($text) { return trim(strtoupper($text));}include('string_handlers.php');

如果string_handlers.php文件也定义了一个uppercase_trim函数,你会收到一条错误消息:Fatal error: Cannot redeclare uppercase_trim()

在PHP中可以用命名空间避免这个问题,Ruby通过模块也提供了类似的功能。但Node的做法是不给你不小心污染全局命名空间的机会。PHP命名空间和Ruby模块 PHP命名空间在它的手册

上有相关论述。Ruby模块在Ruby文档中有解释说明。

Node模块打包代码是为了重用,但它们不会改变全局作用域。比如说,假设你正用PHP开发一个开源的内容管理系统(CMS),并且想用一个没有使用命名空间的第三方API库。这个库中可能有一个跟你的程序中同名的类,除非你把自己程序中的类名或者库中的类名改了,否则这个类可能会搞垮你的程序。可是修改程序中的类名可能会让那些以你的CMS为基础构建项目的开发人员遇到问题。如果修改那个库中的类名,那么每次更新程序源码树中的那个库时都得记着再改一次。解决命名冲突问题最好的办法是从根本上予以避免。

Node模块允许从被引入文件中选择要暴露给程序的函数和变量。如果模块返回的函数或变量不止一个,那它可以通过设定exports对象的属性来指明它们。但如果模块只返回一个函数或变量,则可以设定module.exports属性。图2-2展示了这一工作机制。

图2-2 组装module.exports属性或exports对象让模块可以选择应该把什么跟程序共享

如果你觉得有点晕,先别急。我们在这一章里会给出好几个例子。Node的模块系统避免了对全局作用域的污染,从而也就避免了命名冲突,并简化了代码的重用。模块还可以发布到npm(Node包管理器)存储库中,这是一个在线存储库,收集了已经可用并且要跟Node社区分享的Node模块,使用这些模块没必要担心某个模块会覆盖其他模块的变量和函数。

为了帮你把逻辑组织到模块中,我们会讨论下面这些主题:● 如何创建模块;● 模块放在文件系统中的什么地方;● 在创建和使用模块时要意识到的东西。

我们这就深入到Node模块系统的学习中去,开始一个新的Node项目,然后创建第一个简单的模块。2.2 开始一个新的Node项目

创建新的Node项目很简单:创建一个文件夹,运行npm init。好了!npm命令会问几个问题,一直回答yes就可以了。

下面是一个完整的例子:mkdir my_moudlecd my_moudlenpm init -y

参数 -y表示yes。这样npm就会创建一个全部使用默认值的package.json文件。如果你想要更多的控制权,去掉参数 -y,你就能看到npm提出的一系列问题,包括授权许可、作者姓名,等等。完成之后看一下package.json,你会在其中发现自己提供的那些答案。你也可以手动编辑,但记得必须是有效的JSON。

空项目有了,可以创建模块了。创建模块

模块既可以是一个文件,也可以是包含一个或多个文件的目录,如图2-3所示。如果模块是一个目录,Node通常会在这个目录下找一个叫index.js的文件作为模块的入口(这个默认设置可以重写,见2.5节)。

图2-3 Node模块可以用文件(例1)或目录(例2)创建

典型的模块是一个包含exports对象属性定义的文件,这些属性可以是任意类型的数据,比如字符串、对象和函数。

为了演示如何创建基本的模块,我们在一个名为currency.js的文件中添加一些做货币转换的函数。这个文件如下面的代码清单所示,其中有两个函数,分别对加元和美元进行互换。代码清单2-1 定义一个Node模块(currency.js)const canadianDollar = 0.91; function roundTwo(amount) { return Math.round(amount * 100) / 100;}exports.canadianToUS = canadian => roundTwo(canadian * canadianDollar); ←---- canadianToUS函数设定在exports模块中,所以引入这个模块的代码可以使用它exports.USToCanadian = us => roundTwo(us / canadianDollar); ←---- USToCanadian也设定在exports模块中

exports对象上只设定了两个属性。也就是说引入这个模块的代码只能访问到canadian-ToUS和USToCanadian这两个函数。而变量canadianDollar作为私有变量仅作用在canadianToUS和USToCanadian的逻辑内部,程序不能直接访问它。

使用这个新模块要用到Node的require函数,该函数以所用模块的路径为参数。Node以同步的方式寻找模块,定位到这个模块并加载文件中的内容。Node查找文件的顺序是先找核心模块,然后是当前目录,最后是node_modules。

关于require和同步I/Orequire是Node中少数几个同步I/O操作之一。因为经常

用到模块,并且一般都是在文件顶端引入,所以把require做

成同步的有助于保持代码的整洁、有序,还能增强可读性。但在I/O密集的地方尽量不要用require。所有同步调用

都会阻塞Node,直到调用完成才能做其他事情。比如你正

在运行一个HTTP服务器,如果在每个进入的请求上都用了

require,就会遇到性能问题。所以require和其他同步操作通

常放在程序最初加载的地方。

下面这个是test-currency.js中的代码,它require了currency.js模块。代码清单2-2 引入一个模块(test_currency.js)const currency = require('./currency'); ←---- 用路径./表明模块跟程序脚本放在同一目录下console.log('50 Canadian dollars equals this amount of US dollars:');console.log(currency.canadianToUS(50)); ←---- 使用currency模块的canadianToUS函数console.log('30 US dollars equals this amount of Canadian dollars:');console.log(currency.USToCanadian(30)); ←---- 使用currency模块的USToCanadian函数

引入一个以./开头的模块意味着,如果你准备创建的程序脚本test-currency.js在currency_app目录下,那currency.js模块文件,如图2-4所示,应该也放在currency_app目录下。在引入时,.js扩展名可以忽略。如果没有指明是js文件,Node也会检查json文件,json文件是作为JavaScript对象加载的。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载