Express基础及源码解析


express是什么

Express是基于Node.js平台的Web开发框架,是一个路由和中间件 Web 框架,其自身只具有最低程度的功能:Express 应用程序基本上是一系列中间件函数调用。核心特性:

  • 设置中间件来响应HTTP请求
  • 定义了路由表来执行不同的HTTP请求动作
  • 可以通过模板传递参数来动态渲染HTML页面

用法

  • 基本路由:路由用于确定应用程序如何响应对特定端点的客户机请求,包含一个 URI(或路径)和一个特定的 HTTP 请求方法(GET、POST 等)。每个路由可以具有一个或多个处理程序函数,这些函数在路由匹配时执行。路由定义采用以下结构:app.METHOD(PATH, HANDLER)
    • app 是 express 的实例
    • METHOD 是 HTTP 请求方法
    • PATH 是服务器上的路径
    • HANDLER 是在路由匹配时执行的函数
  • 中间件
    • 中间件函数能够访问请求对象req、响应对象res以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为next的变量来表示。
    • 中间件函数可以执行以下任务:
      • 执行任何代码
      • 对请求和响应对象进行更改
      • 结束请求/响应循环
      • 调用堆栈中的下一个中间件函数
    • 如果当前中间件函数没有结束请求/响应循环,那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。Express 应用程序可以使用以下类型的中间件:
      • 应用层中间件
      • 路由器层中间件
      • 错误处理中间件
      • 内置中间件
      • 第三方中间件
    • 使用 express.Router 类来创建可安装的模块化路由处理程序。Router 实例是完整的中间件和路由系统;因此,常常将其称为“微型应用程序”。以下示例将路由器创建为模块,在其中装入中间件,定义一些路由,然后安装在主应用程序的路径中。在应用程序目录中创建名为 birds.js 的路由器文件,其中包含以下内容:
      var express = require('express');
      var router = express.Router();
      // middleware that is specific to this router
      router.use(function timeLog(req, res, next) {
      console.log('Time: ', Date.now());
      next();
      });
      // define the home page route
      router.get('/', function(req, res) {
      res.send('Birds home page');
      });
      // define the about route
      router.get('/about', function(req, res) {
      res.send('About birds');
      });
      module.exports = router;
      
      var birds = require('./api');
      //...
      app.use('/api', birds);
      

note: 支持的中间件

执行流程

express内部维护一个函数数组,这个函数数组表示在返回响应之前要执行的函数,也就是中间件数组,每次使用use将中间件推入数组;如果当前中间件没有终结请求-响应循环,则必须调用 next()方法将控制权交给下一个中间件,否则请求就会挂起

源码学习

  • 目录结构
    源码目录结构

  • 入口 express.js : 创建express应用

    function createApplication() {
    /**
     * @desc 使用工厂模式创建一个express的实例对象,将控制移交给handle
     */
    var app = function(req, res, next) {
      app.handle(req, res, next);
    };
    /**
     * @desc 将 EventEmitter.prototype 和 application功能导入app
     * mixin不会改变目标对象的原型,会在原有对象基础上增加(混入)新的属性
     */
    mixin(app, EventEmitter.prototype, false);
    mixin(app, proto, false);
    // Object.create创建一个指定原型和若干属性的新对象
    app.request = Object.create(req, {
      app: { configurable: true, enumerable: true, writable: true, value: app }
    })
    // 继承response模块功能
    app.response = Object.create(res, {
      app: { configurable: true, enumerable: true, writable: true, value: app }
    })
    app.init();
      return app;
    }
    
  • application.js:配置基本设置,配置默认中间件,配置路由映射方法

  • middleware:中间件
    • index.js:初始化req, res
    • query.js:将url参数解析为对象形式并赋值给req.query
      module.exports = function query(options) {
      var opts = merge({}, options)
      var queryparse = qs.parse;
      if (typeof options === 'function') {
        queryparse = options;
        opts = undefined;
      }
      if (opts !== undefined && opts.allowPrototypes === undefined) {
        // back-compat for qs module
        opts.allowPrototypes = true;
      }
      return function query(req, res, next){
        if (!req.query) {
          var val = parseUrl(req).query;//将url解析为对象格式,提取query值
          req.query = queryparse(val, opts);//将参数解析为对象格式
        }
        next();
      };
      };
      
  • router: 路由相关

    • index.js:Router类

      var proto = module.exports = function(options) {
      var opts = options || {};
      
      function router(req, res, next) {
        router.handle(req, res, next);
      }
      // mixin Router class functions
      setPrototypeOf(router, proto)
      router.params = {};
      router._params = [];
      router.caseSensitive = opts.caseSensitive;
      router.mergeParams = opts.mergeParams;
      router.strict = opts.strict;
      router.stack = [];
      return router;
      };
      
    • layer.js:保存路由中间件实体信息

      methods.forEach(function(method){
      Route.prototype[method] = function(){
      var handles = flatten(slice.call(arguments));
      for (var i = 0; i < handles.length; i++) {
        var handle = handles[i];
      
        if (typeof handle !== 'function') {
          var type = toString.call(handle);
          var msg = 'Route.' + method + '() requires a callback function but got a ' + type
          throw new Error(msg);
        }
        debug('%s %o', method, this.path)
        var layer = Layer('/', {}, handle);
        layer.method = method;
      
        this.methods[method] = true;
        this.stack.push(layer);
      }
      return this;
      };
      });
      
    • route.js:route类,处理不同的method

文章作者: Susie Chang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Susie Chang !
 上一篇
ECMAScript6新特性 ECMAScript6新特性
参考资料 ECMAScript 6 入门 ECMAScript 6 vs ECMAScript 5 ES6新增的编程风格 let 块级作用域和const 全局常量 var命令存在变量提升效用,let命令没有这个问题 const 考虑到全局
2018-09-30
下一篇 
拖放 拖放
理解 拖放是一种非常流行的用户界面模式。它的概念很简单:点击某个对象,并按住鼠标按钮不放,将鼠标移动到另一个区域,然后释放鼠标按钮对象将对象放置到该位置; 基本概念:创建一个绝对定位的元素,使其可以用鼠标移动。单元素鼠标拖尾的基本代码需要为
2018-09-24
  目录