注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

snoopyxdy的博客

https://github.com/DoubleSpout

 
 
 

日志

 
 

expressjs源码解读(五) —— 3.0版本route模块(1)  

2011-12-22 15:34:14|  分类: node |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
最后我们来看下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个主要方法:
Route.prototype.match = function(path){
  return this.regexp.exec(path);
};
匹配的方法,传入path参数,然后利用原先定义的path进行匹配,返回Regexp.exe()结果的数组。也就是说如果我们定义了多个路由规则,则会实例化多个Route类。

function normalize(path, keys, sensitive, strict) {...}
这个方法名叫:正常化,其实更贴切的应该是叫 正则化,他会根据传入的path路径还有参数等字符串,拼装成一个正则表达式,供以后math方法使用。具体实现太TM复杂,我不大在行正则就不分析拉,免得献丑。

2、collection.js

function Collection(router) {
  Object.defineProperty(this, 'router', { value: router });
}
定义Collection类的属性router

Collection.prototype.__proto__ = Array.prototype;
将Collection的原型链的原型父链指向Array的原型链,这样Collection就继承了Array.prototype的方法,也就是数组的一些方法。同时他还是Function的一个实例,并且他还有一个属性router。

Collection.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一切皆对象。
比如:

var 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类的核心函数

function 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函数。

Router.prototype.param = function(name, fn){...}
注册一个回调函数,是配合 app.param()方法一起使用的。
我们回过头来先看看app.param:

app.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方法:

Router.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数组中,以供后面获取这个参数时执行回调。

Router.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的对象是什么呢?
Router.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个参数是用来生成正则使用,第一个参数是是否严格匹配,第二个参数是是否区分大小写。

Router.prototype._options = function(req, res){
  var path = parse(req.url).pathname
    , body = this._optionsFor(path).join(',');
  res.header('Allow', body).send(body);
};
这个options函数是用来接收option请求的,将返回服务器所支持的所有请求格式,例如"get,post等等"

两个重头戏:
_dispatch和_match,调度员和比对两个方法我下一节再详细说。
  评论这张
 
阅读(2252)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016