最后我们来看下route模块,之前connect.js的route模块写的太TM坑爹了,相当混乱,作者显然也是发现了这个问题,重新在expressjs中改写了route模块。
新的route模块包含4个文件,我们一个个来看,
1、index.js:主要功能模块
2、collection.js:收集remove的route
3、methods.js:http请求方法的数组
4、route.js:匹配和正则化path
1、route.js
包含2个主要方法:1
2
3Route.prototype.match = function(path){
  return this.regexp.exec(path);
};
匹配的方法,传入path参数,然后利用原先定义的path进行匹配,返回Regexp.exe()结果的数组。也就是说如果我们定义了多个路由规则,则会实例化多个Route类。1
function normalize(path, keys, sensitive, strict) {...}
这个方法名叫:正常化,其实更贴切的应该是叫 正则化,他会根据传入的path路径还有参数等字符串,拼装成一个正则表达式,供以后math方法使用。具体实现太TM复杂,我不大在行正则就不分析拉,免得献丑。
2、collection.js
1  | function Collection(router) {  | 
定义Collection类的属性router1
Collection.prototype.__proto__ = Array.prototype;
将Collection的原型链的原型父链指向Array的原型链,这样Collection就继承了Array.prototype的方法,也就是数组的一些方法。同时他还是Function的一个实例,并且他还有一个属性router。1
2
3
4
5
6
7
8
9
10
11
12Collection.prototype.remove = function(){
  var router = this.router
    , len = this.length
    , ret = new Collection(this.router);
  for (var i = 0; i < len; ++i) {
    if (router.remove(this[i])) {
      ret.push(this[i]);
    }
  }
  return ret;
};
这个代码主要是调用Collection的router属性对象的remove方法删除原本Collection实例的内容复制到另外一个新的Collection示例ret数组中。
看到这里我们发现Collection实例化的数组既有数组的方法,也有属性,这里只要理解javascript一切皆对象。
比如:1
2
3
4
5
6
7var a=[1,2,3,4];
a.msg = '我是数组';
a.alt = function(){
 alert(this.msg);
}
a.alt(); //弹出我是数组
alert(a.length); //弹出4
3、index.js
最重要的一般都放在最后,index.js就是提供route类的核心函数1
2
3
4
5
6
7
8
9
10
11function Router(app) {
  var self = this;
  this.app = app;
  this.routes = new Collection;
  this.map = {};
  this.params = {};
  this._params = [];
  this.middleware = function(req, res, next){
    self._dispatch(req, res, next);
  };
}
传入了一个app参数,这里作者用 this.routes = new Collection; 就是实例化Colection类,比较装B的写法,表示不传参数的new func()。
还定义了一个middleware函数。1
Router.prototype.param = function(name, fn){...}
注册一个回调函数,是配合 app.param()方法一起使用的。
我们回过头来先看看app.param:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23app.param = function(name, fn){
  var self = this
    , fns = [].slice.call(arguments, 1);
  // array
  if (Array.isArray(name)) {
    name.forEach(function(name){
      fns.forEach(function(fn){
        self.param(name, fn);
      });
    });
  // param logic
  } else if ('function' == typeof name) {
    this._router.param(name);
  // single
  } else {
    if (':' == name[0]) name = name.substr(1);
    fns.forEach(function(fn){
      self._router.param(name, fn);
    });
  }
  return this;
};
app.param期望接收2个参数,当然第一个参数是数组时,就将数组中每一项name和回调fn做递归。
当第一个参数是函数时,则expressjs认为是注册一种匹配的函数,转给route.param方法。
最后进行判断name的合法性和fn是否是数组,将1个name和1个fn作为参数转给route.param方法。
然后我们看下route.param方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27Router.prototype.param = function(name, fn){
  // param logic
  if ('function' == typeof name) {
    this._params.push(name);
    return;
  }
  // apply param functions
  var params = this._params
    , len = params.length
    , ret;
  for (var i = 0; i < len; ++i) {
    if (ret = params[i](name, fn)) {
      fn = ret;
    }
  }
  // ensure we end up with a
  // middleware function
  if ('function' != typeof fn) {
    throw new Error('invalid param() call for ' + name + ', got ' + fn);
  }
  (this.params[name] = this.params[name] || []).push(fn);
  return this;
};
如果第一个参数是function,则向route._params数组插入这个function,然后直接return;
执行route._params中的函数将返回函数赋值个头ret
接着合法性判断,然后将name作为key,fn作为值放置到route.param数组中,以供后面获取这个参数时执行回调。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Router.prototype.find = function(fn){
  var len = methods.length
    , ret = new Collection(this)
    , method
    , routes
    , route;
  for (var i = 0; i < len; ++i) {
    method = methods[i];
    routes = this.map[method];
    if (!routes) continue;
    for (var j = 0, jlen = routes.length; j < jlen; ++j) {
      route = routes[j];
      if (fn(route)) ret.push(route);
    }
  }
  return ret;
};
根据methods数组进行循环,methods数组就是methods.js文件,如果map对象中不存有这个方法,也就是用户没有注册过这个方法,则继续查找。
当找到用户注册的方法,然后根据fn回调进行操作,如果返回true,则将route对象存入collection实例的ret中。
最后返回collection实例。
好我们跳着看,那个存入map对象中的以请求method为key的对象是什么呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Router.prototype._route = function(method, path, callbacks){
  var app = this.app
    , callbacks = utils.flatten([].slice.call(arguments, 2));
  // ensure path was given
  if (!path) throw new Error('app.' + method + '() requires a path');
  // create the route
  var route = new Route(method, path, callbacks, {
      sensitive: app.enabled('case sensitive routing')
    , strict: app.enabled('strict routing')
  });
  // add it
  (this.map[method] = this.map[method] || []).push(route);
  this.routes.push(route);
  return this;
};
它就是这个,用_route方法注册map[method]中的route对象。sensitive,strict 这2个参数是用来生成正则使用,第一个参数是是否严格匹配,第二个参数是是否区分大小写。
1  | Router.prototype._options = function(req, res){  | 
这个options函数是用来接收option请求的,将返回服务器所支持的所有请求格式,例如”get,post等等”
两个重头戏:1
_dispatch和_match,
调度员和比对两个方法我下一节再详细说。