node基本概念
什么是node?
node源于js的流行,因而出现性能极强的V8。Node是JavaScript程序的平台,不要把它跟框架相混淆。很多人都误把Node当做JavaScript 上的Rails或Django,实际上它更底层。
使用node开发服务端的几个好处
- 减少客户端服务端的语言切换,实现代码共享
- JSON是JavaScript原生支持格式
- 有些NoSQL数据库(MongoDB和CouchDB)用的就是js
- js是一门编译目标语言,很多语言可以编译成js
- Node用的虚拟机(V8)紧跟ECMAScript标准
- Node非常适合数据密集实时型系统(data-intensive real-time)
Node的异步IO原理图
基本模块
除了实现宿主环境(浏览器)中的一些常用对象,Node还有一组用来处理多种网络和文件I/O的核心模块。 其中包括用于HTTP、 TLS、 HTTPS、文件系统(POSIX) 、数据报(UDP)和NET(TCP)的模块。这些核心模块刻意做得很小、底层并且简单,只包含要给基于I/O的程序用的组成部分。第三方模块基于这些核心模块,针对常见的问题进行了更高层的抽象。
Node编程基础
Node新手两大基本问题:
- 如何组织代码
- 如何异步逻辑顺序执行
如何组织代码
Node模块采用CommonJS 模块规范。
路径引用
关于require和同步
require是Node中少数几个同步I/O操作之一。因为经常用到模块,并且一般都是在文件顶端引入,所以把require做成同步的有助于保持代码的整洁、有序,还能增强可读性。但在程序中I/O密集的地方尽量不要用require。所有同步调用都会阻塞Node,直到调用完成才能做其他事情。比如你正在运行一个HTTP服务器,如果在每个进入的请求上都用了require,就会遇到性能问题。所以通常都只在程序最初加载时才使用require和其他同步操作。
用node_modules重用模块
Node中有一个独特的模块引入机制, 可以不必知道模块在文件系统中的具体位置。这个机制就是使用node_modules目录。
如果引用模块并未告知模块位置,require(common.js)
则采用以下的方法来搜寻这个模块。
是否核心->递归向上查找node_modules目录->环境变量NODE_PATH->抛出异常
注意
-
引用本地文件夹指定输出文件
模块多引用缓存(monkey patching)
Node能把模块作为对象缓存起来。 如果程序中的两个文件引入了相同的模块, 第一个文件会把模块返回的数据存到程序的内存中, 这样第二个文件就不用再去访问和计算模块的源文件了。实际上第二个引入有机会修改缓存的数据。这种“猴子补丁” (monkey patching)让一个模块可以改变另一个模块的行为,开发人员可以不用创建它的新版本。
异步编程技术
概念
事件会触发响应逻辑,在node世界里,存在两种:回调与事件监听。
回调通常用来定义一次性、单个响应的逻辑。事件监听本质上也是回调,不同的是,它跟一个概念实体(事件)相关联,能添加多个响应实体。
一个Node HTTP服务器实例就是一个事件发射器,一个可以继承、能够添加事件发射及处理能力的类(EventEmitter) 。
Node的异步回调惯例:Node中的大多数内置模块在使用回调时都会带两个参数:第一个是用来放可能会发生的错误的,第二个是放结果的。
var fs = require('fs');
fs.readFile('./titles.json', function(er, data) {
if (er) throw er; // do something with data if no error has occurred
});
异步逻辑的顺序化
让一组异步任务顺序执行的概念被Node社区称为流程控制。这种控制分为两类:串行和并行。
串行流程控制
可以使用一堆嵌套回调,但是可读性差。
使用第三放库,如Nimble。
TODO:underscore,backbone
并行流程控制
社区中的很多附加模块都提供了方便好用的流程控制工具。其中比较流行的有Nimble、Step 和Seq三个。
Node Web
基本用法:
const http = require('http')
//req,res均为数据流,详情见http模块
const server = http.createServer((req,res)=>{
res.end('Hello World')
})
server.listen(3000)
构建RESTful Web服务
RESTful Web服务,一个使用HTTP方法谓词提供精简API的服务。
POST(其余暂略)
const http = require('http')
const server = http.createServer((req, res) => {
// node的http解析器读入并解析请求数据时,会将数据解析成data事件的形式
// 只要读入了新的数据块,就会触发data事件
// data事件会提供Buffer对象,这是Node版的字节数组。最好将流编码设定为ascii或utf8;这样data事件会给出字符串
req.setEncoding('utf8')
req.on('data',(chunk) => {
})
req.on('close',() => {
res.end()
})
})
设定Content-Length头
为了提高响应速度,如果可能的话,应该在响应中带着Content-Length域一起发送。设定Content-Length域会隐含禁用Node的块编码,因为要传输的数据更少,所以能提升性能。
res.setHeader('Content-Length
,Buffer.byteLength(body))`
Content-Length的值应该是字节长度,不是字符长度,并且如果字符串中有多字节字符,两者的长度是不一样的。为了规避这个问题,Node提供了一个Buffer.byteLength()方法。
用formidable处理上传的文件
提供静态文件服务
通过管道,将req需要的文件,读入输入流,管道到res输出流。
res.setHeader('Content-Lenght',stat.size)
let stream = fs.createReadStream(path)
stream.pipe(res)
stream.on('error',(err)=>{
sendResult(res, 500,'Internal Server Error');
})
存储Node程序中的数据
选择合适的存储机制取决于以下五个因素:
- 存储什么数据;
- 为了保证性能,要有多快的数据读取和写入速度;
- 有多少数据;
- 要怎么查询数据;
- 数据要保存多久,对可靠性有什么要求。
为此,一般有三种选择:
- 存储数据而无需安装和配置DBMS:文件、内存(并发问题)
- 用关系型数据库存储数据,具体说就是MySQL和PostgreSQL
- 用NoSQL数据库存储数据,具体说就是Redis、MongoDB和Mongoose
关系型数据库管理系统
var http = require('http')
var work = require('./lib/timetrack')
var mysql = require('mysql')
var qs = require('querystring')
// 1.创建数据库
var db = mysql.createConnection({
host:'127.0.0.1',
user:'myuser',
password:'mypassword',
database:'timetrack',
port:3000
})
// 2.创建server
var server = http.createServer((req,res)=>{
switch(req.method){
case 'POST':
switch(req.url){
case '/':
// 一般是路由函数,内部会有一些解析请求的辅助函数,用于实现模块化
work.add(db,req,res)
break
case 'archive':
work.archive(db,req,res)
break
case 'delete':
work.delete(db,req,res)
break
}
break
case 'GET':
switch(req.url){
case '/':
work.show(db,res)
break
case 'archive':
work.archive(db,res)
}
break
}
})
// 3.初始化数据库,建表
db.query(
"GREATE TABLE IF NOT EXISTS work ("
+ "id INT(10) NOT NULL AUTO_INCREMENT, "
+ "hours DECIMAL(5,2) DEFAULT 0, "
+ "date DATE, "
+ "archived INT(1) DEFAULT 0, "
+ "description LONGTEXT, "
+ "PRIMARY KEY(id)",
(err) => {
if(err) throw err
console.log('Server started...')
server.listen(3000,'127.0.0.1')
}
)
MongoDB & Redis
尽管关系型DBMS为可靠性牺牲了性能,但很多NoSQL数据库把性能放在了第一位。因此,对于实时分析或消息传递而言,NoSQL数据库可能是更好的选择。NoSQL数据库通常也不需要预先定义数据schema, 对于那种要把数据存储在层次结构中, 但层次结构却会发生变化的程序而言,这很有帮助。
Redis
Redis非常适合处理那些不需要长期访问的简单数据存储,比如短信和游戏中的数据。教程尝试Redis是个很好的起点。要深入学习如何使用Redis,请看Josiah L. Carlson的Redis in Action一书(Manning, 2013)。
MongoDB
MongoDB数据库把文档存在集合(collection)中。它们不需要相同的schema,每个文档都可以有不同的schema。 这使得MongoDB比传统的RDBMS更灵活,因为你不用为预先定义schema而操心。
Mongoose
MongoDB是一个强大的数据库,而node-mongodb-native提供了高性能的MongoDB访问,但你可能想用一个抽象的数据库访问API,在底层帮你处理细节。这可以让你加快开发速度,同时维护更少的代码。这些API中最流行的是Mongoose。
Mongoose的模型(模型视图控制器中的说法)提供了一个到MongoDB集合接口,以及一些实用的功能,比如schema层次结构,中间件以及数据校验。 schema层次结构可以让一个模型跟其他模型关联,比如说,让一篇博客文章包含相关的评论。中间件可以转换数据,或在操作模型数据的过程中触发逻辑,让删除父数据时对子数据的修剪这样的任务变成自动化的。 Mongoose的校验支持让你可以在schema层面决定什么样的数据是可接受的,而不是必须手工处理它。