前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >node框架express的研究

node框架express的研究

作者头像
lhyt
发布于 2022-09-21 11:55:58
发布于 2022-09-21 11:55:58
1K00
代码可运行
举报
文章被收录于专栏:lhyt前端之路lhyt前端之路
运行总次数:0
代码可运行

0.前言

在node中,express可以说是node中的jQuery了,简单粗暴,容易上手,用过即会,那么我们来试一下怎么实现。下面我们基于4.16.2版本进行研究

1. 从入口开始

1.1入口

主入口是index.js,这个文件仅仅做了require引入express.js这一步,而express.js暴露的主要的函数createApplication,我们平时的var app = express();就是调用了这个函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

//var EventEmitter = require('events').EventEmitter;
//var mixin = require('merge-descriptors');
//用了merge-descriptors这个包混合两个对象(包括set、get),也可用assign
  mixin(app, EventEmitter.prototype, false); 
  mixin(app, proto, false);

  // expose the prototype that will get set on requests
  app.request = Object.create(req, { //在app加上一个属性,它的值是一个对象,继承于req
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init(); //初始化
  return app;
}

1.2 proto

我们也看见有proto这个东西,其实他是var proto = require('./application');这样来的,而这个文件就是给app这个对象写上一些方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var app = exports = module.exports = {};
app.init = function (){}
app.handle = function (){}
app.use = function (){}
app.route = function (){}
//此外,下面的还有listen,render,all,disable,enable,disabled,set,param,engine等方法

上面我们已经把这个application.js的app对象和express.js里面的app对象混合,也就是express.js这个文件里面的app.handle、app.init也是调用了这个文件的

1.2.1 app.init方法

其实就是初始化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.init = function init() {
  this.cache = {};
  this.engines = {};
  this.settings = {}; //存放配置
  this.defaultConfiguration(); //默认配置
};

defaultConfiguration默认配置:已省略一些代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.defaultConfiguration = function defaultConfiguration() {
  // 默认设置
  this.enable('x-powered-by');
  this.set('etag', 'weak');
  this.set('env', env);
    // 让app继承父的同名属性
    setPrototypeOf(this.request, parent.request)
    setPrototypeOf(this.response, parent.response)
    setPrototypeOf(this.engines, parent.engines)
    setPrototypeOf(this.settings, parent.settings)
  });

  // setup locals
  this.locals = Object.create(null);

  // top-most app is mounted at /
  this.mountpath = '/';

  // default locals
  this.locals.settings = this.settings;

  // default configuration
  this.set('view', View);
  this.set('views', resolve('views'));
  this.set('jsonp callback name', 'callback');
};

我们再看app.set

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.set = function set(setting, val) {
  if (arguments.length === 1) {
    // 只传一个参数直接返回结果
    return this.settings[setting];
  }

  //对settings设置值
  this.settings[setting] = val;

  // 值的匹配,具体过程略过
  switch (setting) {
    case 'etag':
      break;
    case 'query parser':
      break;
    case 'trust proxy':
      break;
  }
  return this;
};

1.2.2 app.handle方法

把回调函数先写好

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.handle = function handle(req, res, callback) {
  var router = this._router;
  // 路由匹配成功触发回调
  var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  });
  // 没有路由
  if (!router) {
    done();
    return;
  }
  router.handle(req, res, done);
};

那么,我们的this._router来自哪里?

1.2.3 每一个method的处理

我们的this._router来自 this.lazyrouter()方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//methods是常见的http请求以及其他一些方法名字的字符串数组
methods.forEach(function(method){
//app.get app.post等等我们常用的api
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    this.lazyrouter();

    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1)); //就是常用的route.get('/page',callback)
    return this;
  };
});

1.2.4 app.lazyrouter产生this._router

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.lazyrouter = function lazyrouter() {
  if (!this._router) {
    this._router = new Router({ //新建路由
      caseSensitive: this.enabled('case sensitive routing'),
      strict: this.enabled('strict routing')
    });

    this._router.use(query(this.get('query parser fn')));
    this._router.use(middleware.init(this));
  }
};

2. Router(下文讲到router和route,注意区分)

实际上就是var Router = require('./router');,于是我们打开router这个文件夹。

  • index.js: Router类,他的stack用于存储中间件数组,处理所有的路由
  • layer.js 中间件实体Layer类,处理各层路由中间件或者普通中间件;
  • route.js Route类,用于处理子路由和不同方法(get、post)的路由中间件

2.1 index.js文件

上面我们也看见了new一个新路由的过程,index.js用于处理存储中间件数组。在router文件夹下的index.js里面,暴露的是proto,我们require引入的Router也是proto:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var proto = module.exports = function(options) {
  var opts = options || {};

  function router(req, res, next) {
    router.handle(req, res, next);
  }

  // router也获得了proto 的方法了
  setPrototypeOf(router, proto)

  router.params = {};
  router._params = [];
  router.caseSensitive = opts.caseSensitive;
  router.mergeParams = opts.mergeParams;
  router.strict = opts.strict;
  router.stack = []; //栈存放中间件

  return router;
};
//接下来是proto的一些方法:proto.param 、proto.handle 、proto.process_params、proto.use、proto.route
//后面是对于methods加上一个all方法,进行和上面methods类似的操作
methods.concat('all').forEach(function(method){
  proto[method] = function(path){ //route.all, route.get, router.post
    var route = this.route(path)
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});

this.route方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, { //layer是中间件实体
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));

  layer.route = route; //注意这里

  this.stack.push(layer); //中间件实体压入Router路由栈
  return route;
};

2.2 layer.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);//无new调用
  }

  var opts = options || {};
  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // 路径匹配相关设置
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {//参数是req,res,next,长度是3,这时候有next了
    return next();
  }
  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};

Layer.prototype后面接着handle_error(处理错误)、match(正则匹配路由)、decode_param(decodeURIComponent封装)方法,具体不解释了

3. 中间件

我们平时这么用的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.use((req,res,next)=>{
  //做一些事情
  //next() 无next或者res.end,将会一直处于挂起状态
});
app.use(middlewareB);
app.use(middlewareC);

3.1 app.use

使用app.use(middleware)后,传进来的中间件实体(一个函数,参数是req,res,next)压入路由栈,执行完毕后调用next()方法执行栈的下一个函数。中间件有app中间件和路由中间件,其实都是差不多的,我们继续回到路由proto对象(也就是Router对象):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
proto.use = function use(fn) {
  var offset = 0; //表示从第几个开始
  var path = '/';//默认是/

//如果第一个参数不是函数,app.use('/page',(req,res,next)=>{})
  if (typeof fn !== 'function') {
    var arg = fn;

//考虑到第一个参数是数组
    while (Array.isArray(arg) && arg.length !== 0) { //一直遍历,直到arg不是数组
      arg = arg[0];
    }

    // first arg is the path
    if (typeof arg !== 'function') {
      offset = 1; //如果第一个参数不是函数,从第二个开始
      path = fn; //app.use('/page',(req,res,next)=>{}),第一个参数是路径
    }
  }

  var callbacks = flatten(slice.call(arguments, offset)); //数组扁平化与回调函数集合
  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];
    // 增加中间件
    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;  //如果是路由中间件就是一个Route对象,否则就是undefined。
//如果是路由中间件,在index.js的proto.route方法里面,给layer实例定义layer.route = route 
    this.stack.push(layer);//压入Router对象的栈中
  }
  return this;
};

3.2 route.js文件对methods数组处理

这个文件是用于处理不同method的,后面有一段与前面类似的对methods关键代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
methods.forEach(function(method){
  Route.prototype[method] = function(){    //app.get('/page',(req,res,next)=>{})就这样子来的
    var handles = flatten(slice.call(arguments));
    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];
      var layer = Layer('/', {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }


    return this;
  };
});

3.3 中间件种类

普通与路由中间件

  • 普通中间件:app.use,不管是什么请求方法,只要路径匹配就执行回调函数
  • 路由中间件:根据HTTP请求方法的中间件,路径匹配和方法匹配才执行 所以有两种Layer:
  • 普通中间件Layer,保存了name,回调函数已经undefined的route变量。
  • 路由中间件Layer,保存name和回调函数,route还会创建一个route对象 还有,中间件有父子之分:

Router与Route

Router类的Layer实例对象layer.route为undefined表示这个layer为普通中间件;如果layer.route是Route实例对象,这个layer为路由中间件,但没有method对象。而route对象的Layer实例layer是没有route变量的,有method对象,保存了HTTP请求类型,也就是带了请求方法的路由中间件。

所以Router类中的Layer实例对象是保存普通中间件的实例或者路由中间件的路由,而Route实例对象route中的Layer实例layer是保存路由中间件的真正实例。

  • Route类用于创建路由中间件,并且创建拥有多个方法(多个方法是指app.get('/page',f1,f2...)中的那堆回调函数f1、f2...)的layer(对于同一个路径app.get、app.post就是两个layer)保存stack中去。
  • Router类的主要作用是创建一个普通中间件或者路由中间件的引导(layer.route = route),然后将其保存到stack中去。
  • Route类实例对象的stack数组保存的是中间件的方法的信息(get,post等等),Router类实例对象的stack数组保存的是路径(path)

4. 模板引擎

我们平时这样做的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.set('views', path.join(__dirname, 'views')); //设置视图文件夹
app.set('view engine', 'jade'); //使用什么模板引擎

//在某个请求里面,使用render
res.render('index');  //因为设置了app.set('view engine', 'jade'); ,所以我们不用res.render('index.jade');

set方法前面讲过,给setting对象加上key-value。然后我们开始调用render函数

4.1 从res.render开始

我们来到response.js,找到这个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
res.render = function render(view, options, callback) {
  var app = this.req.app;
  var done = callback;
  var opts = options || {};
  var req = this.req;
  var self = this;
  if (typeof options === 'function') {
    done = options;
    opts = {};
  }

  opts._locals = self.locals;
  done = done || function (err, str) { //我们不写callback的时候,如res.render('index',{key:1}); 
    if (err) return req.next(err);
    self.send(str); //把对象转字符串发送
  };
  app.render(view, opts, done);
};

我们发现最后来到了app.render,我们简化一下代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
app.render = function render(name, options, callback) {
  var cache = this.cache;
  var done = callback;
  var engines = this.engines;
  var opts = options;
  var renderOptions = {};
  var view;
//对renderOptions混合 this.locals、opts._locals、opts
  merge(renderOptions, this.locals);
  if (opts._locals) {
    merge(renderOptions, opts._locals);
  }
  merge(renderOptions, opts);

  if (!view) {//第一次进,如果没有设置视图
    var View = this.get('view');
    view = new View(name, { //引用了view.js的View类
      defaultEngine: this.get('view engine'),
      root: this.get('views'),
      engines: engines
    });

  tryRender(view, renderOptions, done); //渲染函数,内部调用view.render(options, callback);
};

4.2 view.js

view.render方法在此文件中找到,实际上它内部再执行了this.engine(this.path, options, callback)。而engine方法是在View构造函数里面设置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function View(name, options) {
  var opts = options || {};
  this.defaultEngine = opts.defaultEngine;
  this.ext = extname(name);
  this.name = name;
  this.root = opts.root;
  var fileName = name;
  if (!this.ext) {
    this.ext = this.defaultEngine[0] !== '.'
      ? '.' + this.defaultEngine
      : this.defaultEngine;
    fileName += this.ext;
  }

  if (!opts.engines[this.ext]) {
    var mod = this.ext.substr(1) //获取后缀 ejs、jade
 // 模板引擎对应express的处理函数,具体内容大概是把模板转为正常的html,这里不研究了
    var fn = require(mod).__express 

    if (typeof fn !== 'function') { //如果模板引擎不支持express就报错
      throw new Error('Module "' + mod + '" does not provide a view engine.')
    }
    opts.engines[this.ext] = fn 
    
    //当然,application.js也有类似的设置,现在是无opts.engines[this.ext]的情况下的设置
    //若app.engine设置过了就不会来到这里了
  }

  this.engine = opts.engines[this.ext];  // 设置模板引擎对应于express的编译函数
  this.path = this.lookup(fileName);// 寻找路径
}

那么this.engine(this.path, options, callback)实际上就是require(mod).__express(this.path, options, callback),如果那个模板引擎支持express,那就按照他的规则走

看见一些文章说中间件用connect模块做的,我看了一下connect的确是可以,而且形参一模一样,但是我看源码里面压根就没有connect的影子。connect应该算是早期的express吧

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-07-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
express4.0源码解析
express4.X 跟3.X 有很大区别,4.X 去除了connect的依赖,3.X基于connect的中间件基本全部不能用,如果还有可以使用的,也是4.X重写的。所以要想继续使用这些熟悉的中间件,就要手动安装依赖包,或者用一些其他的中间件。
frontoldman
2019/09/02
7770
手写Express.js源码
上一篇文章我们讲了怎么用Node.js原生API来写一个web服务器,虽然代码比较丑,但是基本功能还是有的。但是一般我们不会直接用原生API来写,而是借助框架来做,比如本文要讲的Express。通过上一篇文章的铺垫,我们可以猜测,Express其实也没有什么黑魔法,也仅仅是原生API的封装,主要是用来提供更好的扩展性,使用起来更方便,代码更优雅。本文照例会从Express的基本使用入手,然后自己手写一个Express来替代他,也就是源码解析。
蒋鹏飞
2020/11/04
5.6K0
手写Express.js源码
express的application.js里的路由代码
application.js是express框架的核心,也是里面包括了服务端的很多配置和逻辑代码。这里主要说一下和路由有关的一些代码。
theanarkh
2019/03/06
3K0
Express version 4.17核心源码解析
Express的源码、以及目前现在主流库已经全部使用TypeScript编写,呼吁大家全面切换到TypeScript
Peter谭金杰
2020/05/09
5790
Express version 4.17核心源码解析
Express4.x API (一):application (译)
写在前面 Express文档核心的四大部分app,request,response,router,基本上已经完成。简单的总结 通过调用express()返回得到的app实际上是一个JavaScript
okaychen
2018/01/05
3.1K0
多维度分析 Express、Koa 之间的区别
Express 历史悠久相比 Koa 学习资料多一些,其自带 Router、路由规则、View 等功能,更接近于 Web FrameWork 的概念。Koa 则相对轻量级,更像是对 HTTP 的封装,自由度更多一些,官方 koajs/koa/wiki 提供了一些 Koa 的中间件,可以自行组合。
用户1462769
2020/03/28
1.6K0
Express 框架
以下实例中我们引入了 express 模块,并在客户端发起请求后,响应 "Hello World" 字符串。
acc8226
2022/09/30
2.4K0
Express学习笔记
Express方法源于 HTTP 方法之一,附加到 express 类的实例。它可请求的方法包括:
kif
2023/02/27
3.9K0
Express学习笔记
《Node.js+Express+Vue项目实战》-- 1.安装和使用Express(笔记)
Express 是一个精简、灵活的 Node.js 的 Web 应用程序开发框架,为 Web 和移动应用程序提供了一组强大的功能,使用 Express 可以快速地开发一个 Web 应用。
爱学习的程序媛
2022/04/07
4.8K0
《Node.js+Express+Vue项目实战》-- 1.安装和使用Express(笔记)
从源码分析express/koa/redux/axios等中间件的实现方式
在前端比较熟悉的框架如express、koa、redux和axios中,都提供了中间件或拦截器的功能,本文将从源码出发,分析这几个框架中对应中间件的实现原理。
周陆军博客
2023/05/14
2.1K0
Express-路由篇
接触到一个新的框架时,首先要了解的就是路由,路由是指应用程序的端点 (URI) 如何响应客户端请求,简单来讲就是定义通过什么路径来访问各个服务,每个路由可以有一个或多个处理函数,当路由匹配时执行。
iwhao
2024/07/31
1750
Express框架快速入门
官网对这个框架的解释是:基于 Node.js 平台,快速、开放、极简的 Web 开发框架。Express的官网地址是https://www.expressjs.com.cn 。
害恶细君
2022/11/22
5.5K0
Express框架快速入门
express的router.js源码分析(router/index.js)
router.js的代码其实是router/index.js,里面的代码是express的路由的核心和入口。下面我们看一下重要的代码。
theanarkh
2019/03/06
1.2K0
express框架layer.js源码解析
layer.js是express框架的路由机制的底层数据结构。下面为主要源码,已经删除一些不太重要的代码。
theanarkh
2019/03/06
2.9K0
express框架route.js源码解析
route.js并不是express里真正的路由代码,他只是其中的一个组成部分,和router(router/index.js)是有区别的。下面先看一下重要的代码。
theanarkh
2019/03/06
7280
08_Express框架
使用Node.js进行服务器开发,开发效率比较低,例如在实现路由功能和静态源访问功能时,代码写起来很烦琐 为了提高Node.js服务器的开发效率,人们开发了Express框架,它可以帮助开发人员快速创建网站应用程序。
张哥编程
2024/12/13
2980
08_Express框架
Node.js学习笔记(三)——Node.js开发Web后台服务
Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。 使用 Express 可以快速地搭建一个完整功能的网站。使用Node.js作为AngularJS开发Web服务器的最佳方式是使用Express模块。
张果
2022/09/28
8.4K0
Node.js学习笔记(三)——Node.js开发Web后台服务
Express框架
Express是一个基于Node平台的web应用开发框架,它提供了一系列的强大特性,帮助你创建各种Web应用。我们可以使用 npm install express 命令进行下载。
梨涡浅笑
2020/11/11
2.1K0
Express框架
Express4.x API (四):Router (译)
Express4.x API 译文 系列文章 已经完成了Express4.x API中的Requst和Response对象的文档翻译。简单的总结,request对象即表示HTTP请求,包含了请求查询字
okaychen
2018/01/05
2.2K0
Express 中间件
去年刚入职不久参与公司Mean技术栈的培训,其中有share过Express的东西,由于当时没有参与过实际项目,对Express理解并不深刻。后来有幸参与ShuttleBus项目,在实际使用中对Express有了些许了解,这里就把自己的想法写出来。
李振
2021/11/26
1.5K0
相关推荐
express4.0源码解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验