nodejs2&Express
Node.js借助事件驱动,非阻塞I/O模型变得轻量和高效,非常适合运行在分布式设备的数据密集型的实时应用。
1、我们只需知道三点就知道exports和module.exports的区别了:
a、module.exports 初始值为一个空对象 {}
b、exports 是指向的 module.exports 的引用
c、require() 返回的是 module.exports 而不是 exports
1、a、require可加载.js、.json和.node后缀的文件;b、require的过程是同步的;c、require目录的机制是:如果目录下有package.json并指定了main字段,则用之;如果不存在package.json,则依次尝试加载目录下的index.js和index.node。d、require过的文件会加载到缓存,所以多次require同一个文件(模块)不会重复加载。e、判断是否是程序的入口文件有两种方式:require.main === module(推荐)、module.parent === null。
2、环境变量不属于Node.js的知识范畴,只不过我们在开发Node.js应用时经常与环境变量打交道,简单来讲,环境变量就是传递参数给运行程序的。
3、package.json对于Node.js应用来说是一个不可或缺的文件,它存储了该Node.js应用的名字、版本、描述、作者、入口文件、脚本、版权等等信息。语义化版本(semver)即dependencies、devDependencies和peerDependencies里的如:”co”: “^4.6.0”。
semver 格式:主版本号.次版本号.修订号。版本号递增规则如下:
主版本号:做了不兼容的API修改
次版本号:做了向下兼容的功能性新增
修订号:做了向下兼容的bug修正
4、直接使用npm i安装的模块是不会写入package.json的dependencies(或devDependencies),npm i express –save –save-exact (安装express,同时将”express”: “4.14.0”写入dependencies),将固定版本号写入dependencies,建议线上的Node.js应用都采取这种锁定版本号的方式,因为你不可能保证第三方模块下个小版本是没有验证bug的,即使是很流行的模块。运行以下命令:npm config set save-exact true。这样每次npm i xxx –save的时候会锁定依赖的版本号,相当于加了–save-exact参数。
为了彻底锁定依赖的版本,让你的应用在任何机器上安装的都是同样版本的模块(不管嵌套多少层),通过运行npm shrinkwrap,会在当前目录下产生一个npm-shrinkwrap.json,里面包含了通过node_modules计算出的模块的依赖树及版本。npm shrinkwrap只会生成dependencies的依赖,不会生成devDependencies的。只要目录下有npm-shrinkwrap.json则运行npm install的时候会优先使用npm-shrinkwrap.json进行安装,没有则使用package.json进行安装。
5、npm config set命令将配置写到了~/.npmrc文件,运行npm config list查看。
6、npm的scripts有一些内置的缩写命令,如常用的:npm start等价于npm run start、npm test等价于npm run test。
7、node –v8-options 查看harmony细节。express使用了path-to-regexp模块实现的路由匹配。从http://node.green上可以看到Node.js各个版本对ES6的支持情况。
8、在开发过程中,每次修改代码保存后,我们都需要手动重启程序,才能查看改动的效果。使用supervisor可以解决这个繁琐的问题,supervisor会监听当前目录下node和js后缀的文件,当这些文件发生改动时,supervisor会自动重启程序。
8、Promise用于异步流程控制,生成器与yield也能实现流程控制(基于co),async/await结合Promise也可以实现流程控制。
9、下面介绍几个常用的req的属性:
a、req.query:解析后的url中的querystring,如?name=haha,req.query的值为{name: ‘haha’}
b、req.params:解析url中的占位符,如/:name,访问/haha,req.params的值为{name: ‘haha’}
c、req.body:解析后的请求体,需使用相关的模块,如body-parser,请求体为{“name”: “haha”},则req.body为{name: ‘haha’}
10、在实际开发中通常有几十甚至上百的路由,都写在index.js既臃肿又不好维护,这时可以使用express.Router实现更优雅的路由解决方案。我们将/和/users/:name的路由分别放到了routes/index.js和routes/users.js中,每个路由文件通过生成一个express.Router实例router并导出,通过app.use挂载到不同的路径。
10、模板引擎(Template Engine)是一个将页面模板和数据结合起来生成html的工具。模板引擎有很多,ejs是其中一种,因为它使用起来十分简单,而且与express集成良好。通过app.set设置模板引擎为ejs和存放模板的目录。ejs有3种常用标签:<% code %>—运行JavaScript代码,不输出;<%= code %>—显示转义后的HTML内容;<%- code %>—显示原始HTML内容。1
2
3
4
5<ul>
<% for(var i=0; i<supplies.length; i++) {%>
<li><%= supplies[i] %></li>
<% } %>
</ul>
11、res.render函数渲染ejs模板,res.render第一个参数是模板的名字,第二个参数是传给模板的数据。res.render的作用就是将模板和数据结合生成html,同时设置响应头中的Content-Type: text/html,告诉浏览器我返回的是html,不是纯文本,要按html展示。
12、我们讲解了express中路由和模板引擎ejs的用法,但express的精髓并不在此,在于中间件的设计理念。中间件返回的响应是随意的,可以响应一个HTML错误页面、一句简单的话、一个JSON字符串,或者其他任何您想要的东西。
13、中间件与next:express中的中间件(middleware)就是用来处理请求的,当一个中间件处理完,可以通过调用next()传递给下一个中间件,如果没有调用next(),则请求不会往下传递,如内置的res.render其实就是渲染完html直接返回给客户端,没有调用next(),从而没有传递给下一个中间件。
14、通过app.use加载中间件,在中间件中通过next将请求传递到下一个中间件,next可接受一个参数接收错误信息,如果使用了next(error),则会返回错误而不会传递到下一个中间件。app.use有非常灵活的使用方式。
15、express有成百上千的第三方中间件,在开发过程中我们首先应该去npm上寻找是否有类似实现的中间件,尽量避免造轮子,节省开发时间。express@4之前的版本基于connect这个模块实现的中间件的架构,express@4及以上的版本则移除了对connect的依赖自己实现了,理论上基于connect的中间件(通常以connect-开头,如connect-mongo)仍可结合express使用。
15、next(new Error(‘haha’)),express内置了一个默认的错误处理器,为我们自动返回了错误栈信息(Express内置了一个错误处理句柄,它可以捕获应用中可能出现的任意错误。这个缺省的错误处理中间件将被添加到中间件堆栈的底部。如果你向next()传递了一个error,而你并没有在错误处理句柄中处理这个error,Express内置的缺省错误处理句柄就是最后兜底的)。假如我们想手动控制返回的错误内容,则需要加载一个自定义错误处理的中间件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var express = require('express');
var app = express();
app.use(function(req, res, next) {
console.log('1');
next(new Error('haha'));
});
app.use(function(req, res, next) {
console.log('2');
res.status(200).end();
});
//错误处理
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!'); //500状态一样可以response文本内容
});
app.listen(3000);
// Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise those requests will “hang” and will not be eligible for garbage collection.
15、如果向next()传入参数(除了‘route’字符串),Express会认为当前请求有错误的输出,因此跳过后续其他非错误处理和路由/中间件函数。如果需做特殊处理,需要创建新的错误处理路由,如下所示。如果路由句柄有多个回调函数,可使用‘route’参数跳到下一个路由句柄。比如:1
2
3
4
5
6
7
8
9
10
11
12
13app.get('/a_route_behind_paywall',
function checkIfPaidSubscriber(req, res, next) {
if(!req.user.hasPaid) {
// 继续处理该请求
next('route');
}
}, function getPaidContent(req, res, next) {
PaidContent.find(function(err, doc) {
if(err) return next(err);
res.json(doc);
});
});
//在这个例子中,句柄getPaidContent会被跳过,但app中为/a_route_behind_paywall定义的其他句柄则会继续执行。
1 | //Dispatch a req, res into the router. |
16、注意:中间件的加载顺序很重要!比如:通常把日志中间件放到比较靠前的位置,后面将会介绍的connect-flash中间件是基于session的,所以需要在express-session后加载。
16、express项目目录结构:
对应文件及文件夹的用处:
a、models: 存放操作数据库的文件。
b、public: 存放静态文件,如样式、图片等。
c、routes: 存放路由文件。
d、views: 存放模板文件。
e、index.js: 程序主文件。
f、package.json: 存储项目名、描述、作者、依赖等等信息。
17、npm i config-lite connect-flash connect-mongo ejs express express-formidable express-session marked moment mongolass objectid-to-timestamp sha1 winston express-winston –save
18、Express:url占位符:xxx—req.params.xxx;get请求—req.query;post请求—经过body-parser中间件—req.body;文件上传请求—经过multer中间件(用于处理enctype=”multipart/form-data”(设置表单的MIME编码)的表单数据)—req.files[0];cookie获取—经过cookie-parser中间件—req.cookies。
19、不管是小项目还是大项目,将配置与代码分离是一个非常好的做法。我们通常将配置写到一个配置文件里,如 config.js 或 config.json ,并放到项目的根目录下。但通常我们都会有许多环境,如本地开发环境、测试环境和线上环境等,不同的环境的配置不同,我们不可能每次部署时都要去修改引用 config.test.js 或者 config.production.js。config-lite 是一个轻量的读取配置文件的模块。config-lite 会根据环境变量(NODE_ENV)的不同从当前执行进程目录下的 config 目录加载不同的配置文件。如果不设置 NODE_ENV,则读取默认的 default 配置文件,如果设置了 NODE_ENV,则会合并指定的配置文件和 default 配置文件作为配置,config-lite 支持 .js、.json、.node、.yml、.yaml 后缀的文件。如果程序以 NODE_ENV=test node app 启动,则通过 require(‘config-lite’) 会依次降级查找 config/test.js、config/test.json、config/test.node、config/test.yml、config/test.yaml 并合并 default 配置; 如果程序以 NODE_ENV=production node app 启动,则通过 require(‘config-lite’) 会依次降级查找 config/production.js、config/production.json、config/production.node、config/production.yml、config/production.yaml 并合并 default 配置。
20、由于我们博客页面是后端渲染的,所以只通过简单的 (GET) 和
1 | // express/lib/application.js |
1 | // 设置模板全局常量 |
27、我们使用express-formidable处理form表单(包括文件上传)。Set-Cookie中的比较重要的属性:secure:当secure值为true时,cookie在HTTP中是无效,在HTTPS中才有效,httpOnly:是微软对COOKIE做的扩展。如果在COOKIE中设置了“httpOnly”属性,则通过程序(JS脚本、applet等)将无法读取到COOKIE信息,防止XSS攻击产生。expires/Max-Age 字段为此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。express-session中的store选项表示session的存储方式,默认存放在内存中,也可以使用redis,mongodb等。express生态中都有相应模块的支持。
28、Express已经将Session管理的整个实现过程简化到仅仅几行代码的配置的地步了,你完全不用理解整个session产生、存储、返回、过期、再颁发的结构,使用Express和Redis实现Session管理,只要两个中间件就足够了:express-session、connect-redis。req在经过session中间件的时候就会自动完成session的有效性验证、过期/重新颁发、以及对session中数据的获取了。注销操作:在req.session上调用destroy()方法来清空session就可以了。
29、session.save():把session中的数据重新保存到store中,用内存的内容去替换掉store中的内容。这个方法在HTTP的响应后自动被调用。如果session中的数据被改变了(这个行为可以通过中间件的很多的配置来改变),正因为如此这个方法一般不用显示调用。但是在长连接的websocket中这个方法一般需要手动调用。session.touch():更新maxAge属性,一般不需要手动调用,因为session的中间件已经替你调用了。也就是把session的maxAge设置为构造Session对象的时候的初始值。
30、每一个路由都可以有一个或者多个处理器函数,当匹配到路由时,这个/些函数将被执行。路由的定义由如下结构组成:app.METHOD(PATH, HANDLER)。其中,app是一个express实例;METHOD是某个HTTP请求方式中的一个;PATH是服务器端的路径;HANDLER是当路由匹配到时需要执行的函数。
31、将静态资源文件所在的目录作为参数传递给express.static中间件就可以提供静态资源文件的访问了。例如,假设在public目录放置了图片、CSS和JavaScript文件,你就可以:app.use(express.static(‘public’));现在,public目录下面的文件就可以访问了。http://localhost:3000/images/kitten.jpg。所有文件的路径都是相对于存放目录的,因此,存放静态文件的目录名不会出现在URL中。如果你希望所有通过express.static访问的文件都存放在一个“虚拟(virtual)”目录(即目录根本不存在)下面,可以通过为静态资源目录指定一个挂载路径的方式来实现,如下所示:app.use(‘/static’, express.static(‘public’));现在,你就可以通过带有”/static”前缀的地址来访问public目录下面的文件了。http://localhost:3000/static/images/kitten.jpg。
32、Express支持任何符合(path, locals, callback)接口规范的模板引擎。通过app.engine(ext, callback)方法即可创建一个你自己的模板引擎。其中,ext指的是文件扩展名、callback是模板引擎的主函数,接受文件路径、参数对象和回调函数作为其参数。1
2
3
4
5
6
7
8
9
10
11
12var fs = require('fs'); //此模板引擎依赖fs模块
app.engine('ntl', function (filePath, options, callback) { //定义模板引擎
fs.readFile(filePath, function (err, content) {
if (err) return callback(new Error(err));
//这是一个功能极其简单的模板引擎
var rendered = content.toString().replace('#title#', '<title>'+ options.title +'</title>')
.replace('#message#', '<h1>'+ options.message +'</h1>');
return callback(null, rendered);
})
});
app.set('views', './views'); //指定视图所在的位置
app.set('view engine', 'ntl'); //注册模板引擎
33、如何处理404:在Express中,404并不是一个错误(error)。因此,错误处理器中间件并不捕获404。这是因为404只是意味着某些功能没有实现。也就是说,Express执行了所有中间件、路由之后还是没有获取到任何输出。你所需要做的就是在其所有他中间件的后面添加一个处理404的中间件。
34、如何设置一个错误处理器:错误处理器中间件的定义和其他中间件一样,唯一的区别是4个而不是3个参数,即(err, req, res, next):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
35、如何渲染纯HTML文件:不需要!无需通过res.render()渲染HTML。你可以通过res.sendFile()直接对外输出HTML文件。
36、通过Express应用生成器工具可以快速创建一个应用的骨架。通过如下命令安装:npm install express-generator -g。在当前工作目录下创建一个命名为myapp的应用:express myapp。Express 4应用生成器生成的app.js是一个Node模块,不能作为应用(除非修改代码)单独启动,需要通过一个Node文件加载并启动,这里这个文件就是node ./bin/www。创建或启动Express应用时,bin目录或者文件名没有后缀的www文件都不是必需的,它们只是生成器推荐的做法,请根据需要修改。通过Express应用生成器创建的应用一般都有如下目录结构:
37、在Express中,app.use把中间件加入一个栈中,http request将触发整个中间件链条,并依次执行(通过next()函数实现),功能类似于filter,但其作用却大于filter,它可以动态地给req,res添加内容。
38、路由句柄:可以为请求处理提供多个回调函数,其行为类似中间件。唯一的区别是这些回调函数有可能调用next(‘route’)方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。
39、响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。
40、可使用express.Router类创建模块化、可挂载的路由句柄。Router实例是一个完整的中间件和路由系统,因此常称其为一个“mini-app”。
41、Express是一个自身功能极简,完全是由路由和中间件构成一个的web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。中间件(Middleware)是一个函数,中间件的功能包括:a、执行任何代码。b、修改请求和响应对象。c、终结请求-响应循环。d、调用堆栈中的下一个中间件。如果当前中间件没有终结请求-响应循环,则必须调用next()方法将控制权交给下一个中间件,否则请求就会挂起。Express应用可使用如下几种中间件:应用级中间件;路由级中间件;错误处理中间件;内置中间件;第三方中间件。使用可选择挂载路径,可在应用级别或路由级别装载中间件。另外,你还可以同时装载一系列中间件函数,从而在一个挂载点上创建一个子中间件栈。
42、 app.use([path,] function [, function…]):Mounts the middleware function(s) at the path. If path is not specified, it defaults to “/”.
43、应用级中间件:应用级中间件绑定到app对象,使用app.use()和app.METHOD(),其中,METHOD是需要处理的HTTP请求的方法,例如GET、PUT、POST等等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var app = express();
// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
44、如果需要在中间件栈中跳过剩余中间件,调用next(‘route’)方法将控制权交给下一个路由。注意:next(‘route’)只对使用app.VERB()或router.VERB()加载的中间件有效。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制权交给栈中下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染常规页面
res.render('regular');
});
// 处理 /user/:id, 渲染一个特殊页面
app.get('/user/:id', function (req, res, next) {
res.render('special');
});
45、路由级中间件:路由级中间件和应用级中间件一样,只是它绑定的对象为express.Router()。var router = express.Router();路由级使用router.use()或router.VERB()加载。
46、错误处理中间件:错误处理中间件有4个参数,定义错误处理中间件时必须使用这4个参数。即使不需要next对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。
47、内置中间件:从4.x版本开始,Express已经不再依赖Connect了。除了express.static,Express以前内置的中间件现在已经全部单独作为模块安装使用了。
48、第三方中间件:通过使用第三方中间件从而为Express应用增加更多功能。安装所需功能的node模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。
49、在Express中使用模板引擎,需要在应用中进行如下设置才能让Express渲染模板文件:a、views:放模板文件的目录,比如:app.set(‘views’, ‘./views’);b、view engine:设置模板引擎,比如:app.set(‘view engine’, ‘jade’)。
50、调试Express:Express内部使用debug模块记录路由匹配、使用到的中间件、应用模式以及请求-响应循环。debug有点像改装过的console.log,不同的是,您不需要在生产代码中注释掉debug。它会默认关闭,而且使用一个名为DEBUG的环境变量还可以打开。当应用收到请求时,能看到Express代码中打印出的日志。
51、X-Forwarded-For:简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP,只有在通过了HTTP代理或者负载均衡服务器时才会添加该项。
52、app.route():app.route()方法可为路由路径创建链式路由句柄。由于路径在一个地方指定,会让路由更加模块化,也能减少代码冗余和拼写错误。1
2
3
4
5
6
7
8
9
10app.route('/book')
.get(function(req, res) {
res.send('Get a random book');
})
.post(function(req, res) {
res.send('Add a book');
})
.put(function(req, res) {
res.send('Update the book');
});
53、express.Router类是一个完整的中间件和路由系统。创建一个模块化的路由,并加载中间件,然后定义一些路由,并且在主应用中将其挂载到指定路径。
54、集成数据库:为Express应用添加连接数据库的能力,只需要加载相应数据库的Node.js驱动即可。
55、The order in which you define middleware with router.use() is very important. They are invoked sequentially, thus the order defines middleware precedence. For example, usually a logger is the very first middleware you would use, so every request is logged.1
2
3
4
5
6var logger = require('morgan');
router.use(logger());
router.use(express.static(__dirname + '/public'));
router.use(function(req, res){
res.send('Hello');
});
Now suppose you wanted to ignore logging requests for static files, but to continue logging routes and middleware defined after logger(). You would simply move static() above:1
2
3
4
5router.use(express.static(__dirname + '/public'));
router.use(logger());
router.use(function(req, res){
res.send('Hello');
});
56、为了跨机器安装得到一致的结果,Yarn需要比你配置在package.json中的依赖列表更多的信息。Yarn需要准确存储每个安装的依赖是哪个版本。为了做到这样,Yarn使用一个你项目根目录里的yarn.lock文件。
57、yarn global 是一个命令前缀,可用于 add、bin、list 和 remove 等命令。 它们的行为和他们的普通版本相同,只是它们用一个全局目录来存储包。
58、npm install 命令用来安装模块到node_modules目录。安装之前,npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。如果你希望,一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f或–force参数。
59、如果想更新已安装模块,就要用到npm update命令。它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。到registry这个网址下载压缩包,在本地解压,就得到了模块的源码。
60、npm install或npm update命令,从registry下载压缩包之后,都存放在本地的缓存目录。(Registry代理服务)
61、用npm root -g 查看全局包安装的路径。查询本地或全局的命令。
62、