1

前言

本文将介绍Node.js的一些基本概念,包含它的历史,特性和简单的使用等。如果你有过服务端的编程经验,那么你将能很快熟悉它。

Node.js是什么

图片描述

这是Node.js官网上对其的定义,就是说,Node.js是一个javascript运行平台,该平台基于chrome v8 引擎。V8让Node.js在性能上得到了巨大的提升,因为它去掉了中间环节,执行的不是字节码,用的也不是解释器,而是直接编译成了本地机器码。(注意:v8 5.9 发布后,Ignition 字节码解释器将默认启动,v8 又回到了字节码的怀抱,具体请参阅:https://cnodejs.org/topic/590...

Node.js带来的好处

对一名前端而言,Node无疑有如下几个好处:

  1. 开发人员用一种语言就能编写整个Web应用,这可以减少开发客户端和服务端时所需的语言切换。
  2. 有些NoSQL数据库中用的就是JavaScript语言,(比如CouchDB和MongoDB),所以跟它们配合使用非常方便。
  3. Node用的虚拟机(V8)会紧跟ECMAScript标准。换句话说,在Node中如果想用新的JavaScript语言特性,不用等到所有浏览器都支持。

特点

Node.js采用的是事件驱动、非阻塞I/O的设计模型。大家想到了什么?这和javascript在浏览器上的运行机制是一样的。
另外,对于高并发的处理,传统平台采用的是多线程方案,而Node.js则采用的是单线程、事件驱动、非阻塞I/O的设计模型。

我们来看一个浏览器中的例子

$.post('/resource.json', function (data) {
  console.log(data)
})
// 继续执行

上面代码是浏览器中的一个ajax请求,假如该请求需要耗费600ms,该ajax请求会在事件轮询的外面执行(脚本执行的主顺序之外),然后当这个ajax请求完成时(600ms后),它会发出一个“事件”,会有一个函数(通常称作“回调”)来处理它。
这个操作是异步的,并不会“阻塞”脚本执行,事件轮询仍然可以响应页面上执行的其他交互或请求。这样,浏览器可以对客户做出响应,并且可以处理页面上的很多交互动作。

我们再来看一个服务器中的例子:

// 数据库查询操作
db.query('SELECT * FORM work', function (data) {
    console.log(data)
  }
)

这段代码做了些I/O操作,并且在所有数据回来之前,这个进程并不会被阻塞。在Node中,I/O几乎总是在主事件轮询之外进行,使得服务器可以一直处于高效并且随时能够做出响应的状态,就像NGINX(带有异步I/O的事件轮询的一种http服务器)一样。这样进程不会受I/O限制,因为I/O延迟不会拖垮服务器。因此一些在服务器上曾经是重量级的操作,在Node服务器上仍然可以是轻量级的。

dirt程序

Node所针对的应用程序有一个专门的简称:DIRT。它表示数据密集型实时(data-intensive real-time)程序。上面已经提到,Node自身在I/O上非常轻量,能在处理大量请求时保持很多开放的连接,并且只占用一小部分内存,所以,它特别擅长处理数据密集型实时应用。比如在线文档协作、对临近公交车的实时精确定位,以及多人在线游戏等。

简单的例子

接下来我们先看一些简单的例子。

文件操作

var fs = require('fs')

fs.readFile('./package.json', 'utf8', function (er, data) {
  console.log(data)
})

这段程序是要从硬盘里读取package.json文件。当所有数据都读出来后,就会调用那个回调函数。require('fs')是指加载Node提供的文件模块,读取的文件内容将用utf8进行编码。

HTTP服务器

如果你有 PHP 开发经验,会知道在成功运行 PHP 之前先要配置一个功能强大而复杂的 HTTP 服务器,譬如Apache、IIS 或 Nginx,还需要将 PHP 配置为 HTTP 服务器的模块,或者使用 FastCGI 协议调用 PHP 解释器。这种架构是“浏览器 - HTTP 服务器 - PHP 解释器”的组织方式,而Node.js 将“HTTP服务器”这一层抽离,直接面向浏览器用户。如下图所示:
图片描述

接下来,让我们创建一个 HTTP 服务器吧。建立一个名为 app.js 的文件,代码如下:

var http = require('http')

http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'})
  res.write('<h1>Node.js</h1>')
  res.end('<p>Hello World</p>')
}).listen(3000)

console.log("HTTP server is listening at port 3000.")

上面代码中,创建HTTP服务器调用了http.createServer()函数。它只有一个参数,是个回调函数,服务器每次收到HTTP请求后都会调用这个回调函数。这个请求回调有两个参数,请求和响应对象,通常简写为req和res。服务器每收到一条HTTP请求,都会用新的req和res对象触发请求回调函数
res.writeHead()方法设定了响应状态码为200和响应头中的Content-Type为text/html类型。
response.write()方法表示向请求的客户端发送响应内容。 内容可以是一个Buffer或字符串,表示要发送的内容。
Node不会自动往客户端写任何响应。在调用完请求回调函数之后,就要由你负责用res.end()方法结束响应。所以,最终程序用res.end('<p>Hello World</p>')结束了一次响应。

在终端中运行node app.js 命令,打开浏览器访问 http://127.0.0.1:3000 试试吧。

Node在处理数据流上也很强大。你可以把数据流看成特殊的数组,只不过数组中的数据分散在空间上,而数据流中的数据是分散在时间上的。通过将数据一块一块地传送,开发人员可以每收到一块数据就开始处理,而不用等所有数据都到全了再做处理。
下面我们看一个例子:

var fs = require('fs')
var stream = fs.createReadStream('./package.json')

stream.on('data', function (chunk) {
  console.log(chunk)
})
stream.on('end', function () {
  console.log('finished')
})

只要有新的数据块准备好,就会激发data事件,当所有数据块都加载完之后,会激发一个end事件。
程序可以边读取边处理,这要比等着所有数据都缓存到内存中再处理效率高得多
Node中也有可写数据流,可以往里写数据块。当HTTP服务器上有请求过来时,对其进行响应的res对象就是可写数据流的一种。
可读和可写数据流可以连接起来形成管道,这是一种高效的数据处理方式,只要有数据准备好就可以处理,不用等着读取完整个资源再把它写出去。
用我们上面的HTTP服务器,看看如何把一张图片流到客户端:

var http = require('http')
var fs = require('fs')

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'image/png'})
  fs.createReadStream('./img.png').pipe(res)
}).listen(3000)

console.log('Server running at http://localhost:3000/')

在这行代码中,数据从文件中读进来(fs.createReadStream),然后数据随着进来就被送到(.pipe)客户端(res)。在数据流动时,事件轮询还能处理其他事件。

小结

相信读到这里,大家对Node的优势和特性已经有了一些了解,并且通过本文的三个编程小例子,对Node的一些用法也有了一些体会,Node跟所有技术一样,并不是万能药。它擅长解决某些问题,并为我们带来方便。同样的,在某些方面,却是它的短板,比如:计算密集型应用。希望你能在用Node开发之前,多一些合理的考虑,多一份编程的快乐。


moddx
1.3k 声望70 粉丝

勤能补拙是良训