最近准备系统的学习Node和Express,学习笔记-入门篇!主要内容有背景、HelloWord和简单DEMO。
初识Express
Express特点
精简的、灵活的Node.js Web程序框架,为构建单页、多页以及混合的Web程序提供了一系列健壮的功能特性。
- 精简
这是Express最吸引人的特性之一。Express的哲学是在你的想法和服务器之间充当薄薄的一层。但这并不意味着他不够健壮。或者没有足够的有用特性,而是尽量少干预你,让你充分表达自己的思想,同时提供一些有用的东西。 - 灵活
可扩展。 - Web程序框架
Web程序不能局限于网页或网站,尽管现在“程序”(在你的设备本地运行的东西)和“网页”(通过网络为你的设备服务的东西)之间有明显的界限,但这种界限逐渐变得模糊了,这要感谢PhoneGap这样的项目,也要感谢微软允许HTML5向本地应用程序一样在桌面上运行。不难想象,未来程序和网站之间的界限将不复存在。 - 单页Web请求
Express与Angular或Ember等流行框架配合的很好。单页是比较新颖的想法,不像之前的网站,用户每次访问不同的页面都要发起网络请求。 - 多页和混合的Web请求
Node:新型Web服务器
Node跟其他流行的Web服务器,比如微软的IIS或Apache,有很多共同点。然而更有趣的是探究他的不同之处,所以我们先从讨论他的不同开始。
- Node是单线程的
单线程极大的简化了Web程序的编写,如果你需要多线程程序的性能,可以考虑集群。 - 解释性语言
Node所用的JavaScript引擎(谷歌的V8)确实会将JavaScript编译为本地机器码(更像C或C++),但这一操作是透明的,所以从用户角度来看,表示的还是像纯粹的解释性语言,没有单独的编译步骤,减少了维护和部署的麻烦,你所要做的就是更新JavaScript文件,然后你的修改就自动生效了。 - 平台无关性
他不是第一个或唯一的平台无关的服务器技术,但是平台无关的水品真是良莠不齐。比如Linux运行.NET程序或windows运行PHP程序,可以做到但这一过程会十分痛苦。而所有的操作系统windows、OS X和Linux设置Node都易如反掌,并且协作也十分简单。
Node的生态系统
- 所有主流关系型数据库
MySQL,MariaDB,PostgreSQL,Oracle,SQL Server都支持,因为忽视哪些已经成熟的巨无霸太不明智了 - NoSQL数据库
Node的出现带动了一种新式的存储方式,用否定的方式定义有时不恰当,更准确的称之为“文档数据库”或“键/值对数据库”,这种数据库有很多,但MongoDB是其中的佼佼者。 - 模板引擎
- 授权
Node生态系统的美好也体现在大量可用的开发包上。然而那些包都有其自身的授权,甚至更糟,每个包可能还要依赖其他包。好消息就是Node开发包中最常见的MIT授权,他是毫不费力的许可,几乎允许你做任何想做的事情,包括开发包放到闭源的软件中,然而你不能假定实用的所有包都是MIT授权。
从Node开始
使用终端
也叫控制台或命令行,如果你是使用OS X或Linux,有大量历史悠久的shell(命令终端解释器)可供选择,如果你是使用windows,那么事情就不是那么美好啦,微软从不注重在终端上提供令人愉悦的体验,所以你只能多做点工作,比如Git中包含的Git bash的shell,提供了类似Unix的终端体验,是一个精简的bash shell,但是它用的仍然是内置的Windows控制台程序,因此用起来比较费力,因此推荐安装Console2或ConEmu。
- bash
- Git bash
- putty
- Console2
- powerShell:微软自己的PowerShell,但出于一致性的考虑,不推荐使用。
选定了shell之后,建议花时间熟悉一下与它相关的基础知识。最基本知识如下:
- 切换目录
- 复制、移动和删除文件
- 中断命令行程序(Ctrl+C)
- 文件中搜索文本
- 搜索文件和目录
- 重定向输出
- 冻结终端(Ctrl+S)、解冻终端(Ctrl+Q)
npm
npm是随处可见的Node开发包管理器,npm不是首字母的缩写,因此没有大写。包管理器的两个主要职责是安装开发包和管理依赖项。
如果你在安装npm包是指定-g表示全局安装,他们会被装在你的windows主目录的一个子目录下,如果用户名中有空格,很多包会出现问题,处于安全考虑,建议选一个没有空格的Windows用户名。
Web服务器
Node所提供的范式和传统的Web服务器不同:你写的就是Web服务器。Node只是给你提供了一个构建Web服务器的框架。在Node中编写Web服务器非常简单,并且你因此取得了对程序的控制权,这是非常值得的。Hello World例子如下:
var http = require('http');
http.createServer(function(req,res){
res.writeHead(200,{'Content-Type':'text/plain'});
res.end('Hello world');
}).listen(3000);
Node中的this
node中的this,不同于浏览器端的this,它表现的更复杂,在不同情形下表现不一样。
- 全局中的this默认是一个空对象。并且在全局中this与global对象没有任何的关系。
- 函数中this指向的是global对象,和全局中的this不是同一个对象。
- 构造函数中this指向的是它的实例,而不是global。
- 全局中this会指向module.exports。
事件驱动编程
Node的核心理念是事件驱动编程,这对程序员而言,意味着你必须知道有哪些事件,以及如何响应这些事件。
上述Hello World的例子中,事件是隐含的,HTTP请求就是要处理的事件,http.createServer方法将函数作为一个参数,每次有HTTP请求发送过来就会调用那个函数。
路由
路由是指向客户端提供它所发出的请求内容的机制,对基于Web的客户端/服务器而言,客户端在URL中指明它想要的内容,具体来说就是路径和查询字符串。
如果直接使用Node而言,则基础代码如下:
var http = require('http');
http.createServer(function(req,res){
// 规范化URL
var path = req.url.replace(/\/?(?:\?.*)?$/,'').toLowerCase();
switch(path){
case:...
}
}).listen(3000);
静态资源服务
项目中内容不会变化的资源,他们都被称为“静态资源”。
用Node提供静态资源只适用于初期的小型项目,对于比较大的项目,你应该会想用
Nginx
或CDN
之类的代理服务器来提供静态资源。
如果你用过Apache或IIS,可能习惯于只是创建一个HTML文件,访问它,然后让它自动发送到客户端。Node不是那样的:我们必须打开文件,读取其中的内容,然后将这些内容发送给浏览器。辅助函数serveStaticFile如下:
var fs = require('fs');
function serveStaticFile(res,path,contentType,responseCode){
if(!responseCode) responseCode = 200;// 缺省200
fs.readFile(__dirname + path,function(err,data){
if(err){
res.writeHead(500,{'Content-Type':'text/plain'});
res.end('500 - Internal Error');
} else {
res.writeHead(responseCode,{'Content-Type':'contentType'});
res.end('data);
}
})
}
fs.readFile是读取文件的异步方法,这个函数有同步版本,fs.readFileSync,但异步思考问题的方式,越早接触越好。
__dirname
会被解析为正在执行的脚本所在的目录,不管什么时候,这个全局变量用起来都很方便,如果不这么做,不同的目录中运行你的程序可能会出现难以诊断的错误。
省时省力的Express
脚手架
脚手架并不是一个新想法,这个想法很简单:大多数的项目都需要一定数量的“套路化”代码,谁会想每次开始新的项目时都重新写一次这些代码呢?对此有个简单的方法,那就是创建一个通用的项目骨架,每次开始新项目时,只需复制这个骨架,或者说是模板。
初始步骤
环境搭建
开始一个新项目的基本步骤如下:
- 创建项目的根目录
- npm init
npm在package.json文件中管理项目的依赖项以及项目的元数据。npm init默认的主文件命名为index.js,如果要使用其他的主文件名,记得修改package.json文件中的main属性。 - 安装依赖项
比如最基础的Express,比如运行npm install --save express
,--save指令会更新package.json文件。因为node_modules随时都可以用npm重新生成,所以我们不会把这个目录保存在我们的代码库中,为了确保把它添加到代码库中,我们可以加入到.gitignore
文件中。
如果你的package.json文件中没有制定一个存储库的URL,以及一个非空的README.md文件,那么你每次运行npm时都会看到警告信息。
Express版Hello World如下:
var express = require('express');
var app = express();
app.set('port',process.env.PORT || 3000);
// 定制404
app.use(function(req,res){
res.type('text/plain');
res.status(404);
res.send('404 - Not Found');
});
// 定制500
app.use(function(err,req,res,next){
res.type('text/plain');
res.status(500);
res.send('500 - Server Error');
})
app.listen(app.get('port'),function(){
console.log('success');
})
添加路由
接下来在404处理器之间添加路由,比如首页和关于:
app.get('/',function(req,res){
res.type('text/plain');
res.send('Home Page');
});
app.get('/about',function(req,res){
res.type('text/plain');
res.send('About Page');
})
app.get是我们添加路由的方法,在Express文档中写的是app.VERB。但这不意味着存在一个叫做VERB的方法,它用来指代HTTP动词(最常见的get和post)。这个方法有两个参数:一个路径和一个函数。
路由就是由这个路径定义的。app.VERB帮助我们做了很多工作:默认忽略大小写或反斜杠,并且在进行匹配时也不考虑查询字符串。路由匹配上之后就会调用你提供的函数,并把请求和响应对象作为参数传给这个函数。
在Express中依然可以使用Node提供的res.writeHead和res.end,但是没有必要也不推荐,因为express做了扩展,方法扩展如下:
- node的res.end --> express的res.send
- node的res.writeHead --> express的res.status/res.type
Express默认的状态码为200,不用显示指定。
定制404和500页面的处理与对普通页面的处理有所区别:用的不是app.get,而是app.use。app.use是Express中添加中间件的一种方法,现在可以把它看做处理所有没有路由匹配路径的处理器。在Express中,路由和中间件的添加顺序至关重要
。如果我们把404处理器放在所有路由上面,那首页和关于页面就不能用啦,访问这些URL都将得到404.
路由还可以使用通配符,比如/about/*。
Express中根据回调函数中参数的个数区分404和500处理器。因此在500处理器中,即使不需要一个'下一步'方法,他也必须包括,以便Express将它识别为一个错误处理程序。
视图和布局
视图与静态资源(比如图片或CSS文件)的区别就是他不一定是静态的:HTML可以动态构建,为每个请求提供定制的页面。
Express支持多种不同的视图引擎,他们有不同层次的抽象。
- Jade-高度抽象,你写的根本不想HTML,因为没有尖括号和结束标签,这样可以少敲多次键盘
- Handlebars-不会chouxiangHTML,你编写的是带有特殊标签的HTML。
此时视图引擎的目录为view/layouts/main.handlebars、views/home.handlebarsvar handlebars = require('express3-handlebars').create({defaultLayout:'main'}); app.engine('handlebars',handlebars.engine); app.set('view engine','handlebars');
布局,又称母版页,在开发网站时,每个页面上肯定有一定数量的HTML是相同的,或者非常接近。在每个页面上重复写这些代码不仅非常繁琐,还有导致潜在的维护困境。布局可以解决这个问题,他为网站上所有页面提供了一个通用的框架。
设置的视图引擎后,路由的res.send方法需要改成res.render方法啦,直接指定文件名即可,不需要后缀。并且我们不在指定内容类型和状态码:视图引擎默认返回text/html的内容类型和200的状态码。
视图和静态文件
Express靠中间件处理静态文件和视图。
static中间件可以讲一个或多个目录指派为包含静态资源的目录,其中的资源不经过任何特殊处理直接发给客户端。你可以在其中放图片,css文件,javascript文件等。
可以在项目下创建public的子目录,然后把static中间件加在所有路由之前:
app.use(express.static(__dirname + '/public'));
static中间件相当于给你想要发送的所有静态文件创建了一个路由,渲染文件并发送给客户端。
访问静态资源时,路径中没有public,因为这个目录对客户端而言是隐形的,static中间件会返回这个文件,并正确设定内容类型。
视图中的静态内容
视图并不只是传递静态HTML的复杂方式,真正的强大之处在于它可以包含动态信息。只需要在render方法中传入数据,然后页面中按照引擎提供的语法正确使用即可!
res.render('about',{data:data})