Better

Ethan的博客,欢迎访问交流

Node与Express开发之入门篇

最近准备系统的学习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,它表现的更复杂,在不同情形下表现不一样。

  1. 全局中的this默认是一个空对象。并且在全局中this与global对象没有任何的关系。
  2. 函数中this指向的是global对象,和全局中的this不是同一个对象。
  3. 构造函数中this指向的是它的实例,而不是global。
  4. 全局中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提供静态资源只适用于初期的小型项目,对于比较大的项目,你应该会想用NginxCDN之类的代理服务器来提供静态资源。

如果你用过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

脚手架

脚手架并不是一个新想法,这个想法很简单:大多数的项目都需要一定数量的“套路化”代码,谁会想每次开始新的项目时都重新写一次这些代码呢?对此有个简单的方法,那就是创建一个通用的项目骨架,每次开始新项目时,只需复制这个骨架,或者说是模板。

初始步骤

环境搭建

开始一个新项目的基本步骤如下:

  1. 创建项目的根目录
  2. npm init
    npm在package.json文件中管理项目的依赖项以及项目的元数据。npm init默认的主文件命名为index.js,如果要使用其他的主文件名,记得修改package.json文件中的main属性。
  3. 安装依赖项
    比如最基础的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做了扩展,方法扩展如下:

  1. node的res.end --> express的res.send
  2. 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。
    var handlebars = require('express3-handlebars').create({defaultLayout:'main'});
    app.engine('handlebars',handlebars.engine);
    app.set('view engine','handlebars');
    
    此时视图引擎的目录为view/layouts/main.handlebars、views/home.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})


留言