第 44 章 node.js

以下是 node.js 官方网站的介绍:

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 Node.js 的包管理器 npm,是全球最大的开源库生态系统。

44.1 Node.js 与 V8 引擎

要了解 Node.js,我们可以先要了解什么是 v8 引擎,可以说 Node.js 的诞生很大程度上归功于 v8 引擎的出现。

我们都知道计算机处理器机器语言,而 JavaScript 是一门高级语言,计算机并不能直接读懂。所以我们需要所谓的引擎(就是一个脚本语言的解释之行器)来将其转化成计算机所能理解的语言。V8 引擎是由 Google 推出的,为其浏览器 Chrome 所设计的开源 JavaScript 引擎。得益于 JIT,编译模式的改变与编译阶段的优化,JavaScript 的性能得到了一个飞跃。

V8 的源代码是用 c++写的,除了对 JavaScript 性能的大幅提升,v8 引擎也提供了“嵌入”的功能,使得开发者也可以在自己的 c++程序中使用“嵌入”的 v8 引擎(所谓嵌入,我理解将 V8 引擎集成到别的软件系统中),从而高效地编译 JavaScript,并加入 c++的 feature。要知道,作为一个底层得多的语言,c++可以实现的 feature 可要比 JavaScript 多得多。举例说明,JavaScript 本身并没有 read 这么一个 function。然而通过 v8,我们可以将其绑定到一个用 c++写的 read callback 上,从而通过 JavaScript 我们也可以直接加载文件了。于是,借助于 v8 种种便利的功能,Node.js 诞生了。

Node.js 是一项服务器技术。我们都知道客户端提出服务请求,而服务器端负责处理请求并提供服务。而对于互联网来说,在 Node.js 之前 JavaScript 是一项完全的客户端技术,被用于浏览器中实现各种动画,对 DOM 的操作等等。而后端,即服务端则是由 PHP、Python、Ruby、Java 等等语言来实现。Node.js 的出现,使得前后端使用同一种语言,统一模型的梦想得以实现。

Node.js 到底解决了 JavaScript 的什么痛点和问题?更好的组织代码,提升复用性。当然在 ES6 中这一点也得到了很大的提升。处理文件与数据库。与互联网进行沟通,以标准化的格式处理请求并发送回答。快速地解决如上问题。同时,Node.js 还带来了许多别的后端技术所不具备,或是不完善的优点,如事件驱动,异步编程,非阻塞式 io 等等。JavaScript 本身语言的特性,以及其的流行程度与社区活跃度给 Node.js 带来了各种意义上的优势。

44.2 长轮询与非阻塞

浏览器给网站发请求的过程一直没怎么变过。当浏览器给网站发了请求。服务器收到了请求,然后开始搜寻被请求的资源。如果有需要,服务器还会查询一下数据库,最后把响应结果传回浏览器。不过,在传统的 web 服务器中(比如 Apache),每一个请求都会让服务器创建一个新的进程(有的是线程,以下不再括号)来处理这个请求。

后来有了 Ajax。有了 Ajax,我们就不用每次都请求一个完整的新页面了,取而代之的是,每次只请求需要的部分页面信息就可以了。这显然是一个进步。但是比如你要建一个 FriendFeed 这样的社交网站(类似人人网那样的刷朋友新鲜事的网站),你的好友会随时的推送新的状态,然后你的新鲜事会实时自动刷新。要达成这个需求,我们需要让用户一直与服务器保持一个有效连接。目前最简单的实现方法,就是让用户和服务器之间保持长轮询(long polling)。

HTTP 请求不是持续的连接,你请求一次,服务器响应一次,然后就完了。长轮训是一种利用 HTTP 模拟持续连接的技巧。具体来说,只要页面载入了,不管你需不需要服务器给你响应信息,你都会给服务器发一个 Ajax 请求。这个请求不同于一般的 Ajax 请求,服务器不会直接给你返回信息,而是它要等着,直到服务器觉得该给你发信息了,它才会响应(消耗服务器资源)。比如,你的好友发了一条新鲜事,服务器就会把这个新鲜事当做响应发给你的浏览器,然后你的浏览器就刷新页面了。浏览器收到响应刷新完之后,再发送一条新的请求给服务器,这个请求依然不会立即被响应。于是就开始重复以上步骤。利用这个方法,可以让浏览器始终保持等待响应的状态。

虽然以上过程依然只有非持续的 Http 参与,但是我们模拟出了一个看似持续的连接状态我们再看传统的服务器(比如 Apache)。每次一个新用户连到你的网站上,你的服务器就得开一个连接。每个连接都需要占一个进程,这些进程大部分时间都是闲着的(比如等着你好友发新鲜事,等好友发完才给用户响应信息。或者等着数据库返回查询结果什么的)。虽然这些进程闲着,但是照样占用内存。这意味着,如果用户连接数的增长到一定规模,你服务器没准就要耗光内存直接瘫了。这种情况怎么解决?解决方法就是刚才上边说的:非阻塞和事件驱动。

这些概念在我们谈的这个情景里面其实没那么难理解。你把非阻塞的服务器想象成一个 loop 循环,这个 loop 会一直跑下去。一个新请求来了,这个 loop 就接了这个请求,把这个请求传给其他的进程(比如传给一个搞数据库查询的进程),然后响应一个回调(callback)。完事了这 loop 就接着跑,接其他的请求。这样下来,服务器就不会像之前那样傻等着数据库返回结果了。如果数据库把结果返回来了,loop 就把结果传回用户的浏览器,接着继续跑。在这种方式下,你的服务器的进程就不会闲着等着。从而在理论上说,同一时刻的数据库查询数量,以及用户的请求数量就没有限制了。服务器只在用户那边有事件发生的时候才响应,这就是事件驱动。

Node.js 的应用是通过 javascript 开发的,然后直接在 Google V8 引擎上跑。用了 Node.js,你就不用担心用户端的请求会在服务器里跑了一段能够造成阻塞的代码了。因为 javascript 本身就是事件驱动的脚本语言。你回想一下,在给前端写 javascript 的时候,更多时候你都是在搞事件处理和回调函数。javascript 本身就是给事件处理量身定制的语言。

Node.js 还是处于初期阶段。如果你想开发一个基于 Node.js 的应用,你应该会需要写一些很底层代码。但是下一代浏览器很快就要采用 WebSocket 技术了,从而长轮询也会消失。在 Web 开发里,Node.js 这种类型的技术只会变得越来越重要。

44.3 NPM 介绍

npm(全称 Node Package Manager,即 node 包管理器)是 Node.js 默认的、以 JavaScript 编写的软件包管理系统。

npm makes it easy for JavaScript developers to share and reuse code, and makes it easy to update the code that you’re sharing, so you can build amazing things.

按照 NPM 官方的介绍,npm 是为 JavaScript 开发人员提供的共享、重用、更新代码的便捷工具。npm 包含三部分:npm 网站、npm 命令行和 npm 包库。

npm 可以管理本地项目的所需模块并自动维护依赖情况,也可以管理全局安装的 JavaScript 工具。

44.3.1 安装

npm 随着 Node.js 自动安装。运行下列命令查看 node 版本:

node -v

查看 npm 的版本:

npm -v

在 Mac 中升级 node.js:

brew upgrade node

44.3.2 更新

npm 的更新很快,因此,即便你使用 nodejs 安装 npm,经过一段时间后,用户就非常有必要更新 npm 到最新版本,在终端中运行如下命令,即可更新 npm 本身:

npm install npm@latest -g

44.3.3 常用 NPM 命令

44.3.3.1 在全局范围内安装与删除包

npm install xxx -g
npm unstiall xxx -g

44.3.3.2 查看所有全局性安装包

npm list -g --depth 0

44.3.3.3 更新全局包

npm update -g

44.3.3.4 在指定目录安装与删除包

npm install xxx
npm unstiall xxx

44.3.3.5 查看项目安装的包

npm ls

44.3.3.6 安装指定版本的包

npm install xxx@x.x.x

44.3.3.7 更新指定包

npm update xxx

44.3.3.8 cnpm

npm 由于仓库在国外,因此在更新时速度较慢(有时甚至无法连接),鉴于此,国内有提供镜像服务的网站。cnpm 就是其中一家。安装命令如下:

npm install -g cnpm --registry=https://registry.npm.taobao.org

cnpm 支持所有 npm 命令,只不过使用中国镜像,故而速度更快。

44.3.3.9 nrm

nrm 是一个 npm 仓库管理工具,可以选择不同镜像。

npm install -g nrm
>nrm ls    /* 列出可用 npm 仓库源 */

  npm ---- https://registry.npmjs.org/
  cnpm --- http://r.cnpmjs.org/
* taobao - https://registry.npm.taobao.org/
  nj ----- https://registry.nodejitsu.com/
  rednpm - http://registry.mirror.cqupt.edu.cn/
  npmMirror  https://skimdb.npmjs.com/registry/
  edunpm - http://registry.enpmjs.org/

 >nrm test  /* 测试所有源的响应速度 */

  npm ---- 888ms
  cnpm --- 494ms
* taobao - 406ms
  nj ----- Fetch Error
  rednpm - Fetch Error
  npmMirror  1286ms
  edunpm - Fetch Error

>nrm use taobao  /* 使用淘宝镜像 */

Registry has been set to: https://registry.npm.taobao.org/

44.4 Node.js 起步

44.4.1 设置NPM配置文件package.json

管理本地npm包的最佳方式是使用package.json文件。package.json文件列出了当前项目依赖的包、设置依赖包的版本、能让你的应用更容易复制,因此更容易和其他开发者共享项目。

package.json文件必须包含nameversion信息。

44.4.1.1 创建package.json文件

进入项目文件夹,执行如下指令:

npm init

使用上述命令可交互式地创建package.json文件。

{
  "name": "yangjh-nodejs",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

44.4.1.2 设定项目依赖包

项目依赖包有两类:运行时依赖的包和开发时依赖的包。

44.4.1.2.1 dependencies

除了手工编辑之外,还可以使用如下命令指定项目依赖包:

npm install <package_name> --save
44.4.1.2.2 devDependencies

除手工编辑外,也可以使用如下命令:

npm install <package_name> --save-dev

44.4.1.3 使用git进行版本控制时的说明

如果使用git进行版本控制,有必要忽略掉node_modules中的内容。可以参考https://www.gitignore.io/生成的具体内容。

44.4.1.4 使用nodemon监视应用的变化

开发者创建的nodejs项目的运行结果,可以通过node命令查看:

node index.js

这种方式比较繁琐,因为每次修改js内容后,我们都需要重新运行node命令查看结果。nodemon扩展包可以帮助我们自动监视nodejs项目中的变化,输出运行结果。要使用nodemon,需要在项目文件夹安装扩展包,然后运行:

yarn add nodemon --dev
./node_modules/.bin/nodemon index.js  //nodemon 需要监视的文件

运行效果如下:

[vagrant@localhost yangjh-nodejs]$ ./node_modules/.bin/nodemon index.js
[nodemon] 1.17.5
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
hello nodejs
[nodemon] clean exit - waiting for changes before restart

44.5 使用模块

44.5.1 使用内置的Node.js模块

Node.js 本身提供了很多模块,比如处理文件的模块,加密用的模块,创建 Web 服务器用的模块等等。大部分 Node.js 内置的模块,你都需要先在项目里载入一下它们,才能使用这些模块提供的功能。比如:

// 加载内置模块
const os = require('os')
console.log(os.hostname())

上面的代码将输出主机名称。

44.5.2 使用第三方Node.js模块

使用第三方模块,需要用npm或者yarn先进行安装,比如:

npm install request --save
# 或者选择yarn 进行安装
yarn add request

然后再在脚本中使用:

// 使用第三方模块
request({
    url: 'https://api.douban.com/v2/movie/top250',
    json: true
},(error,response,body)=>{
    // console.log(JSON.stringify(body,null,2))
})

44.5.3 创建并使用自定义的Node.js模块

首先创建自己的模块,比如在src目录中创建mymodule.js

const hello = () => {
    console.log('message from mymodule')
}

module.exports.hello = hello

然后在脚本中进行导入和使用:

// 加载自建模块
const greeting = require('./src/mymodule')
// 使用自建模块中的方法
greeting.hello()