第 1 章 欢迎进入Node.js的世界

第 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函数setTimeoutsetInterval安排好的。接下来是运行I/O回调,即触发你的回调函数。轮询阶段会去获取新的I/O事件,最后是用setImmediate安排回调。这是一个特例,因为它允许你将回调安排在当前队列中的I/O回调完成之后立即执行。现在你可能还会觉得有点儿抽象,不过只需要记住,尽管Node是单线程的,但你仍然可以用它提供的工具写出可伸缩的高效代码。

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

1.2 ES2015、Node和V8

如果你以前曾因JavaScript没有类而伤心难过,或者被它奇怪的作用域规则搞得头晕脑胀,那你肯定会喜欢我们接下来要讲的内容。Node解决了很多问题!现在你可以创建类了!constlet(代替了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更好用。

constlet是从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 = `
  <div>
    <h1>Hello from Node</h1>
    <p>Welcome, ${user.name}!</p>
  </div>
`;

在ES5中,前面那个例子只能写成这样:

this.body = '\n';
this.body += '<div>\n';
this.body += '  <h1>Hello from Node</h1>\n';
this.body += '  <p>Welcome, ' + user.name + '</p>\n';
this.body += '<div>\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特性。这些特性分为shippingstagedin 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 -vnpm -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-project
cd example-project
npm 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 [::]:5858
connecting to 127.0.0.1:5858 ... ok
break 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_express
cd hello_express
npm init -y
npm 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-start
cd electron-quick-start
npm install && npm start
curl 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应用程序、命令行工具,甚至桌面程序。

目录