写在前面
昨天说的npm看了之后, 觉得有两个指的记录, 再就是写下Express
npm补充
npm2和npm3的区别
就本地安装的包来说, npm2
和npm3
有一个很大的区别, 就是组织包的结构. npm2
组织依赖的包是按照树形组织的. npm3
将其改进为扁平结构.
npm2
会将所依赖的包存放到当前目录的./node_modules/
目录下. 而被安装的包又会依赖其他的包的话, 则会存放到该包的./node_modules
下. 所以, 当依赖结构很复杂的时候, 目录结构会非常深. 不管是性能还是操作上, 体验都不怎么好.
your_project/
node_modules/
module_a@1.0/
node_modules/
module_b@1.0/
module_c@1.0/
node_modules/
module_b@1.0
module_d@1.0/
node_modules/
module_b@2.0/
而在npm3
中, 采用扁平的目录结构, 二级依赖会放到当前目录的node_modules
的里, 与一级包在同一目录.
your_project/
node_modules/
module_a@1.0/
module_b@1.0
module_c@1.0/
module_d@1.0/
node_modules/
module_b@2.0/
尽量都安装到项目目录下的./node_modules
中. 但是, 这样会遇到几个问题.
由于安装顺序不同, ./node_modules
的目录结构也可能不同, 这样导致即使是相同的依赖关系, 目录结构也不一定一样. 例如在npm3
例子中, 如果先安装module_d
的话, module_b@2.0
则会和module_d
同级, 这样module_a
和module_c
所依赖的module_b@1.0
则会在各自的./node_modules
目录下, 即使是重复的. 像下面这样:
your_project/
node_modules/
module_a@1.0/
node_modules/
module_b@1.0
module_c@1.0/
node_modules/
module_b@1.0
module_d@1.0/
module_b@2.0/
虽然一般情况下, 这个并不影响项目运行, 但是如果要达成目录结构一致的话, 解决方法是, 删除./node_modules
目录下的所有文件, 重新安装. 因为npm
会按照依赖包的字符表顺序排序后, 按照这个顺序进行安装.
npm安装全局包遇到权限问题
还有一点, 就是在Linux下安装npm
全局包的话, 会遇到权限问题. 我以前一般是用sudo
添加权限. 而又两个方法更好:
1, 用chown
来改变目录的所有者, 全局包安装在npm config get prefix
目录下, 将这个目录的所有者改成运行项目的人就行.
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
2, 利用npm config set prefix <new_dir>
设置到当前用户拥有读写权限的目录下. 再讲这个路径加到PATH
中.
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH
source ~/.profile
如果不想更改PATH
系统变量, 可以这样. 由于npm
最后会在/usr/local/bin
目录下创建链接文件, 指向包的入口文件, 所以这样也是OK的.
NPM_CONFIG_PREFIX=~/.npm-global npm install -g jshint
Express
在Express
中, 采用的是一种中间件的方式对请求进行处理.
+--------+--------+--------+--------+
req -> | MW1 | MW2 | MW3 | MW4 |
| -----> | -----> | -----> | --- |
| \ | \ | \ | \ |
res <- | <-/ | <-/ | <-/ | <-/ |
+--------+--------+--------+--------+
HTTP
请求会依次经过每个中间件处理, 每个中间件可以选择处理该请求, 返回, 或者交给下一个中间件继续处理. 类似于Java Web的filter. 而末尾的中间件, 可以看做成action
.
写Express
应用一般是这样.
var express = require('express');
var app = express();
// respond with "hello world" when a GET request is made to the homepage
app.get('/', function(req, res) {
res.send('hello world');
});
第一行加载Express
模块, 第二行创建Express
对象. app.get
就是设置一个中间件, 这个中间件只处理GET
请求. app.get
第一个参数是当HTTP
请求的URL
匹配这个参数的时候, 运行第二个参数传入函数.
每个中间件就是一个接受三个参数的函数.
function middleware(req, res, next){
// your code
}
而Express
加载中间件的方式也有很多
app.use(function(req, res, next){
// your code.
});
app.use('url_pattern_string', function(req, res, next){
// your code.
});
使用app
对象有很多方法, 其中, use
表示所有HTTP
请求都需要经过这个中间件, 还有几个针对HTTP
请求类型的, 比如get
, post
, put
, delete
等等. 这些只处理相应类型的HTTP
请求, 不处理除此之外其他类型的请求.
更好的组织代码的方式是利用Router
. 在router
对象上设置各种处理, 然后将这个router
作为中间件使用.
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/index', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
然后再添加到app
中
var express = require('express');
var routes = require('./routes/index');
app.use('/routes', routes);
这样, 访问/routes/index
会首先匹配到route
对象, 然后在这个对象进行下一步的匹配, 匹配到/index
, 就执行这里的方法, 渲染index
模板并返回给浏览器.
总体看来, 可以认为Express
是对HTTP
请求的URL
和中间件进行匹配的. 匹配过程中会找到一些符合的中间件, 执行这些中间件. 中间件有很多种, 有针对某种HTTP
请求类型的中间件, 也有不管HTTP
请求类型, 都要执行的中间件. 这都是通过在设置中间件时, 使用哪种方法设置来区别的.
app.use() // HTTP 所有类型的请求
app.get() // HTTP GET请求
app.post() // HTTP POST请求
...
Express
支持很多请求类型.
最重要的还有中间件接受的参数, req
, res
,和next
.
Request
Request
用来封装HTTP
请求的信息.
路由匹配过程中可以匹配参数.
app.get('/user/:id', function(req, res){
res.end('' + req.params.id);
});
get参数可以在req.query
中访问
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse"
访问HTTP请求头
req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined
其他的方法可以查Express 4.x Request
Response
res.locals针对当前请求, 保存一些信息, 可以被模板引擎直接访问, 也可以在其他的中间件中访问到. 不同请求, locals是新的对象, 请求之间是隔离的.
res.locals.title = 'Express';
res.render('index'); // 在模板引擎中可以直接访问title变量, 值为上面指定的`Express
app.use(function(req, res, next){
res.locals.user = req.user;
res.locals.authenticated = ! req.user.anonymous;
next(); // 下一个中间件可以访问这两个变量
});
通过res可以设置cookie
// res.cookie(name, value [, options])
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
响应有很多种
-
res.json([body])
返回JSON
-
res.jsonp([body])
返回JSONP
-
res.download(path [, filename] [, fn])
, 返回文件, 使浏览器下载 -
res.redirect([status,] path)
返回重定向 -
res.render(view [, locals] [, callback])
进行模板渲染, 然后传输给浏览器
进一步的参考Express 4.x Response
最后
在Koa
出来之前, Express
很肿胀, 到了4.x
的时候, Express将很多东西都作为中间件单独提供, 清爽很多, 而且官网首页的一句话介绍也改了. 有过一段时间, 想学学connect
. 但是放弃了, 觉得Express
更方便一些.
对于Koa
, 使用generator function
的确很好, 但是函数的上下文有点混乱, 等Express的文档看完了, 有空好好学学
ES6`
对于看ES6
的内容来说, 大概看了下, 觉得阮一峰的<ECMAScript 6 入门>写的非常好. JavaScript
语言有很多不符合直觉的地方, 很别扭, 比如:
// Chrome 版本 47.0.2526.111 m (64-bit)
[] + []
// => ""
{} + []
// => 0
[] + {}
// => "[object Object]"
{} + {}
// => NaN
NaN === NaN
// => false
// 还有很多...
JavaScript
也有很多不好的地方. 比如不同浏览器支持的程度不一样, 新的标准出来了, 浏览器要跟上也得过段时间; 不同浏览器又会实验性地拓展内容; 为了使用浏览器未实现的语言标准, 出现了中间层来转换(babel). 为了磨平浏览器对于语言支持的区别, 也出现了shim / polyfill; ES6还没学会呢, ES7就快来了(已经有不少草案了);
虽然JavaScript
作为服务器端脚本, 相对其他的脚本语言有所欠缺, 但是我等相信, 未来更好.