Node.js 基础

    其中的内容包括:

    • Node 的安装

    • 如何使用第三方模块生态

    • 第三方模块的安装

    • 一些简单的使用示例

    • 开发过程中的一些建议和技巧

    在此之前,我假设你已经掌握了 JavaScript 基础知识并且熟悉一些基本的命令行操作。另外,不要臆想通过这一章就全面掌握 Node。但是如果你有心的话,可以去阅读 Node.js 实战

    JavaScript 世界的一大特点就是它选择性非常丰富,Node 的安装也不例外。

    可以在官方下载页面找到各种版本的源代码和安装包文件。建议你使用与自己操作系统对应的安装包进行安装。当然,你也可用使用 apt-get、Homebrew 等包管理器进行安装,如果你系统有的话。具体详见官方的包管理工具的安装。

    如果你使用的是 Mac 或者 Linux 的话,那么我极力推荐你使用 NVM 来安装。Window 系统上的对应程序是 。这些版本管理工具,让你可以在不同版本间进行自由切换。例如,你可以在尝试新版本的特性时,同时在系统中保留一份稳定版。另外,NVM 无需系统管理权限同时卸载也非常容易。而安装过程也只需在终端执行一行命令。

    现在,请在你系统中安装好 Node。

    安装完成后,先动手写个 “Hello World” 来检验一些。在新建的 helloworld.js 中加入一下代码:

    代码中主要就是使用 console.log 来打印字符串 “Hello,world!”,相信对于前端程序员来说并不会感到陌生。

    这里,我们使用命令行来运行这段代码。如果一切正常的话,会出现如下输出:

    在大多数编程语言中,我们都会对代码进行拆分,然后在使用的时候将这些文件引入其中。例如,C 和 C++ 中的 Include,Python 的 import ,Ruby 和 PHP 中的 require。而另外一下语言,如 C# 实战编译时完成跨文件引用的。

    很长一段时间内,JavaScript 官方并不支持这一机制。所以,社区中有人就编写了 RequireJS 这种工具来解决依赖性导入的问题。但是,大多数时候还是通过 <script> 标签来进行文件导入。而Node 通过实现名为 CommonJS 的标准模块,完美的解决了文件导入问题。

    在模块系统部分主要有三大主要内容:内置模块的引入,第三方模块引入,个人似有模块引入。下面,将会对这些内容逐一介绍。

    Node 已经内置了很多实用模块,例如,文件系统模块 fs,工具函数模块 util。

    在 Node 编写的 web 应用中,最常见的任务当属 URL 解析了。浏览器通过特定的 URL 来请求服务器上对应的资源。例如,访问主页、访问关于页面 的网络请求。这些 URL 都以字符串的形式存在,我们需要对其进行解析然后获取更多的信息。这里我们通过对 URL 进行解析来介绍如何引入内置模块。

    内置的 url 模块中暴露的方法不多,不过其中有一个 parse 函数非常有用。它能从 URL 字符串中提取到类似域名和路径等有益信息。

    这里我们使用 require 来实现模块导入,该命令与之前提到的 Include、Import 的作用一致。通过将模块名作为参数,该命令就能成功的返回对应的模块。大多数情况下,该返回的对象是一个 object 对象,但有时也可能会是字符串、数字、或者函数。下面是引入改模块的示例代码:

    1. var parsedURL = url.parse("http://www.example.com/profile?name=barry");
    2. console.log(parsedURL.protocol); // "http:"
    3. console.log(parsedURL.host); // "www.example.com"
    4. console.log(parsedURL.query); // "name=barry

    在上面的代码中,通过 require("url") 返回一个模块对象,然后就可以像使用其他对象一样调用对象的方法。

    如果将这段代码保存到 url-test.js 文件中,你可以使用 node url-test.js 运行代码。你将会看到,协议名,域名、查询条件。

    绝大多数时候,我们在引入模块的时候会用一个同名的变量来接受返回的模块对象。例如,上面就使用 url 来介绍 require("url") 的返回值。

    当然,你完全可以不遵循上面的规则。如果你想的话,你也可以这么干:

    1. var theURLModule = require("url");
    2. var parsedURL = theURLModule.parse("http://www.example.com/profile?name=barry");

    保存变量名和模块名一致只是一个统一风格增加可读性的宽松约定,而不是什么强制规范。

    首先,我们需要了解 package.json 文件。所有的 Node 项目都单独存放在一个文件夹中,而项目如果使用了第三方模块,那么其中必定存在一个名为 package.json 的文件。

    package.json 中的内容非常的简单,一般其中定义了项目名称、版本号、作者,已经项目的外部依赖项。

    在新建的 Node 工程文件夹中,将下面的内容复制到 package.json 中:

    1. {
    2. "name": "my-fun-project",
    3. "author": "Evan Hahn",
    4. "private": true,
    5. "version": "0.2.0",
    6. "dependencies": {}
    7. }

    文件定义好之后,我们就可以直接使用了。

    其实,在进行 Node 安装时实际上还安装了另一个程序:npm 。通常 npm 都被称为 Node 包管理器,而这也是它最大的特色。

    假设,现在需要在应用中导入一个小型的标准模版系统 Mustache。它能将模版字符串转化为真正的字符串,请看代码:

    现在,假设你想通过 Mustache 模块来编写一个简单的 Node 应用来欢迎 Nicolas Cage。

    首先,在工程文件夹的根目录里运行 npm install mustache —save。该命令会新建一个 node_modules 文件夹并将 Mustache 保存到文件夹下。 —save 参数将会把该模块添加到 pakage.json 文件中。此时 pakage.json 文件夹大致如下,其中 Mustache 会使用最新的版本。

    1. {
    2. "author": "Evan Hahn",
    3. "private": true,
    4. "version": "0.2.0",
    5. "dependencies": {
    6. "mustache": "^2.0.0" #A
    7. }

    如果你没有使用 —save 选项的话,虽然也会创建 node_modules 文件夹将把 Mustache 模块保存到同名子目录下,但是 pakage.json 将不会发生任何变化。这里之所以将这些依赖关系保存到 package.json 是为了方便其他开发者在得到工程后直接使用 npm install 完成所有依赖项的安装。另一个原因是 Node 项目在进行代码管理时通常都会忽略 node_modules 文件夹而只保留 package.json。

    安装完成后接下来就是使用了:

    1. var Mustache = require("mustache");
    2. var result = Mustache.render("Hi, {{first}} {{last}}!", {
    3. first: "Nicolas",
    4. last: "Cage"
    5. });
    6. console.log(result);

    保存代码到 mustache-test.js 中并执行 node mustache-test.js 命令。然后你将会看见 Hi,Nicolas Cage! 。

    就是这样简单,这些依赖项安装完成后,你可以像使用内置模块一样进行调用。node_modules 中模块引入的工作直接交给 Node 就行了,你无需担心。

    当然你可以手动添加工程依赖项,并且你还可以指定依赖项的版本。

    前面都是介绍如何使用他人开发好的模块,接下来你将会学到如何去开发一个自己的模块。假设现在随机返回 0 ~ 100 之间的整数。在不引入其他模块的情况下,代码大致如下:

    1. var MAX = 100;
    2. function randomInteger() {
    3. return Math.floor( (Math.random() * MAX) );
    4. }

    这可能与你在浏览器环境下代码差不多,并没有什么特别之处。但是在 Node 中,我们还需要暴露一个变量给外部使用。这样当其他程序在通过 require 进行引入的时候就能获得该变量。在此例中,我们暴露函数 randomInteger 并将代码保存到 random-integer.js 文件中。

    最后一行代码对于 Node 初学者来说可能感觉有点陌生。每个模块只能暴露一个变量,而且必须通过 module.exports 设置。本例中只暴露了一个函数变量,所以 MAX 就作为模块私有变量无法被其他文件所访问。

    module.exports 可以暴露任何变量,虽然本例中是一个函数,但是通常都会是一个对象。当然,你可以暴露字符串或者数组。

    接下来我们就来使用一下这个新模块。在 random-integer.js 同一目录下,新建一个 print-three-random-integers.js 并复制下面的代码:

    1. var randomInt = require("./random-integer"); #A
    2. console.log(randomInt()); // 12
    3. console.log(randomInt()); // 77
    4. console.log(randomInt()); // 8

    除了需要通过点语法指定相对路径之外,其余部分与前面几乎一摸一样。通过 node print-three-random-integers.js 命令,我们可以检查程序的运行效果。不出意外的话,将会有三个 0 ~ 100 之间的随机数会被打印出来。

    如果你尝试运行 node random-integer.js 的话,你还发现并没有任何事情发生。虽然,我们暴露了模块中的函数,但是改函数并不会执行更不会打印任何输出。

    以上部分就是 Node 模块系统的简单入门。

    在第一章中,我用 “烤松饼” 的例子简单的介绍了 Node 中的异步特性。其中的关键点就是,你无法同时做两件事哪怕它们是同时发生的。虽然,在烘焙过程中我可以健身,但是,烤箱毕竟不是我而是个外部事物。

     2. Node.js 基础  - 图2

    Node 的异步工作原理与此类似,例如,你通过浏览器请求 Node 服务器上的一张小猫图片。因为该图片资源太大,所以在进行磁盘读写的时候你可以抽身去处理其他事情。此时,这个磁盘就相当于一个外部资源,我们可以直接处理第二个请求而无需挂起等待费时操作结束。

    Express 中主要有两个外部资源:

    • 涉及文件系统。例如,磁盘文件的读写。
    • 涉及网络处理。例如,接受请求、发送响应。
      在 Node 代码中,这些异步都是通过回调进行处理的。其工作原理和在 web 页面发送 AJAX 请求一样。在发送请求时你会附带一个回调函数,当请求处理完成后你的回调将会被执行。

    例如,现在你正在硬盘上读取文件 myfile.txt 。当读取结束后,你希望能够打印出其中字母 X 出现的次数,代码如下:

    1. var fs = require("fs");
    2. var options = { encoding: "utf-8" };
    3. fs.readFile("myfile.txt", options, function(err, data) {
    4. if (err) {
    5. console.error("Error reading file!");
    6. return;
    7. console.log(data.match(/x/gi).length + " letter X's");
    8. });

    下面我们一步步解释这些代码:

    首先,我们导入 Node 自带的文件系统模块。该模块主要处理文件相关内容,其中大多数都是文件读写功能。本例使用的其中的 readFile 方法。

    在 Node 中大多数回调函数都会设置错误信息 error 作为第一个参数。如果一切正常,该参数将会被设为 null 。否则会将对应的错误信息保存到该参数中。这也是错误处理的最佳实践。有时候这些错误信息并不会导致程序终止执行,但是多数情形下需要对错误做出响应,抛出异常并跳出回调函数。

    这也是 Node 中最常见的回调实践。

    最后,当一切正常时我们使用正则表达式匹配字母 X 并打印其数量。

    好的,下面我们来做个测试。这里,我们在上面代码的结束加上一段,那么会发生什么事情呢?

    1. var fs = require("fs");
    2. var options = { encoding: "utf-8" };
    3. fs.readFile("myfile.txt", options, function(err, data) {
    4. if (err) {
    5. console.error("Error reading file!");
    6. return;
    7. }
    8. console.log(data.match(/x/gi).length + " letter X's");
    9. });
    10. console.log("Hello World!");

    异步文件读取时异步操作,所以这里先打印出来的是 “Hello world!”,然后才是异步函数中的打印操作。

    这就是异步模式强大的地方。当一个外部设备在处理费时操作时,你可以继续运行其他代码。在 web 应用中这意味着相同的时间可以处理更多的请求。

    注意:如果你想了解更多 JavaScript 异步的内容的话,你可以去油管上查看这个。视频中的讲解同时适用于 Node 和浏览器环境。

    上面的这个概念有助你更好地了解 Node 内置的 HTTP 模块,而该模块对于 Express 又极为重要。Node 和 Express 能够构建 web 服务都是基于这个模块中的功能。

    Node 的 HTTP 模块有很多特性(比如,向其他服务器发送网络请求),不过我们将要使用的是其中一个名为 http.createServer 的方法。该方法通过其回调函数来处理每一次的网络请求,并且进行响应。下面代码中我们将所有的响应都设置为了 "hello world" (可以保存到 myserver.js 中)。

    上面的代码由 4 个部分构成。

    首先,我们引入 HTTP 模块并将其保存到变量 http 中。这与之前 URL 模块的操作一致。

    接着,定义了一个请求处理函数 requestHandler 。本书中的几乎所有的代码要么是请求处理函数要么是调用处理函数。该函数有两个参数,request 表示请求对象,而 response 则表示响应对象。request 中包含 URL 路径、user-agent 等信息。而通过调用 response 对象方法 Node 会将响应信息打包好并发送给请求者。

    余下的代码则是指定内置的 HTTP 服务在请求是执行的处理函数以及服务监听的端口号。

    如果你将代码保存到 myserver.js 并执行 node myserver.js 拉起服务。那么,此时你在浏览器中访问 http://localhost:3000,你就会看到:

    你可能也注意到了,每当你发起请求的时候终端控制台都会打印一些信息。当你尝试访问不同 URL 时,虽然控制台打印的信息不同但是得到的响应却都是 “Hello, world!”。控制台打印的信息类似于:

     2. Node.js 基础  - 图4

    请注意上面打印的 URL 信息中并不包含 localhost:3000。虽然看起来显得不那么直观,但是反过来这也是对的。毕竟使用相对路径,我们无需修改就能在任何电脑上部署 Node 应用。

    而 URL 解析的代码大致如下:

    1. function requestHandler(req, res) {
    2. if (req.url === "/") {
    3. res.end("Welcome to the homepage!");
    4. } else if (req.url === "/about") {
    5. res.end("Welcome to the about page!");
    6. } else {
    7. res.end("Error! File not found.");
    8. }

    所有的请求 URL 都可以在这个函数里面完成处理。这样做对于简单的应用来说确实非常简单,但是当应用规模变大之后该函数就会变的臃肿不利于维护。这也是 Express 框架出现的重要原因。

    本章的内容有:

    • Node 的安装

    • 模块系统的使用

    • package.json 文件的介绍

    • 通过 package.json 安装第三放模块依赖项

    • Node 中的异步编程概念。

    原文: