关注公众号“执鸢者”,获取大量教学视频及私人总结面筋并进入业余交换群.
在抖音上有幸看到一个程序员讲述如何浏览源代码,次要分为三步:领悟思维、把握设计、领会细节。
- 领悟思维:只需领会作者设计框架的初衷和目标
- 把握设计:只需领会代码的接口和抽象类以及宏观的设计
- 领会细节:是基于顶层的形象接口设计,逐步开展代码的画卷
基于上述三步法,急不可待的拿Express开刀了。本次源码解析有什么不到位的中央各位读者能够在上面留言,咱们一起交换。
一、领悟思维
在Express中文网上,介绍Express是基于Node.js平台,疾速、凋谢、极简的Web开发框架。在这句话外面能够失去解读出以下几点含意:
- Express是基于Node.js平台,并且具备疾速、极简的特点,阐明其初衷就是为了通过扩大Node的性能来进步开发效率。
- 凋谢的特点阐明该框架不会对开发者过多的限度,能够自在的施展设想进行性能的扩大。
- Express是Web开发框架,阐明作者的定位就是为了更加不便的帮忙咱们解决HTTP的申请和响应。
二、把握设计
了解了作者设计的思维,上面从源码目录、外围设计原理及形象接口三个层面来对Express进行整体的把握。
2.1 源码目录
如下所示是Express的源码目录,相比拟来说还是比较简单的。
├─application.js---创立Express利用后可间接调用的api均在此处(外围)<br/>
├─express.js---入口文件,创立一个Express利用<br/>
├─request.js---丰盛了http中request实例上的性能<br/>
├─response.js---丰盛了http中response实例上的性能<br/>
├─utils.js---工具函数<br/>
├─view.js---与模板渲染相干的内容<br/>
├─router---与路由相干的内容(外围)<br/>
| ├─index.js<br/>
| ├─layer.js<br/>
| └route.js<br/>
├─middleware---与中间件相干的内容<br/>
| ├─init.js---会将新减少在request和response新减少的性能挂载到原始申请的request和response的原型上<br/>
| └query.js---将申请url中的query局部增加到request的query属性上<br/>
2.2 形象接口
对源码的目录构造有了肯定理解,上面利用UML类图对该零碎各个模块的依赖关系进一步理解,为后续源码剖析打好根底。
2.3 设计原理
这一部分是整个Express框架的外围,下图是整个框架的运行流程,一看是不是很懵逼,为了搞清楚这一部分,须要明确四个概念:Application、Router、Layer、Route。
为了明确上述四个概念,先引入一段代码
const express = require('./express');const res = require('./response');const app = express();app.get('/test1', (req, res, next) => { console.log('one'); next();}, (req, res) => { console.log('two'); res.end('two');})app.get('/test2', (req, res, next) => { console.log('three'); next();}, (req, res) => { console.log('four'); res.end('four');})app.listen(3000);
- Application
示意一个Express利用,通过express()即可进行创立。 - Router<
路由零碎,用于调度整个零碎的运行,在上述代码中该路由零碎蕴含app.get('/test1',……)和app.get('/test2',……)两大部分 - Layer
代表一层,对于上述代码中app.get('/test1',……)和app.get('/test2',……)都能够成为一个Layer - Route
一个Layer中会有多个处理函数的状况,这多个处理函数形成了Route,而Route中的每一个函数又成为Route中的Layer。对于上述代码中,app.get('/test1',……)中的两个函数形成一个Route,每个函数又是Route中的Layer。
理解完上述概念后,联合该幅图,就大略能对整个流程有了直观感触。首先启动服务,而后客户端发动了http://localhost:3000/test2的申请,该过程应该如何运行呢?
- 启动服务时会顺次执行程序,将该路由零碎中的门路、申请办法、处理函数进行存储(这些信息依据肯定构造存储在Router、Layer和Route中)
- 对相应的地址进行监听,期待申请达到。
- 申请达到,首先依据申请的path去从上到下进行匹配,门路匹配正确则进入该Layer,否则跳出该Layer。
- 若匹配到该Layer,则进行申请形式的匹配,若匹配形式匹配正确,则执行该对应Route中的函数。
上述解释的比较简单,后续会在细节局部进一步论述。
三、领会细节
通过上述对Express设计原理的剖析,上面将从两个方面做进一步的源码解读,上面流程图是一个常见的Express我的项目的过程,首先会进行app实例初始化、而后调用一系列中间件,最初建设监听。对于整个工程的运行来说,次要分为两个阶段:初始化阶段、申请解决阶段,上面将以app.get()为例来论述一下该外围细节。
3.1 初始化阶段
上面利用app.get()这个路由来理解一下工程的初始化阶段。
首先来看一下app.get()的内容(源代码中app.get()是通过遍历methods的形式产生)
app.get = function(path){ // …… this.lazyrouter(); var route = this._router.route(path); route.get.apply(route, slice.call(arguments, 1)); return this;};
在app.lazyrouter()会实现router的实例化过程
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)); }};
留神:该过程中其实是利用了单例模式,保障整个过程中获取router实例的唯一性。
调用router.route()办法实现layer的实例化、解决及保留,并返回实例化后的route。(留神源码中是proto.route)
router.prototype.route = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route;// 把route放到layer上 this.stack.push(layer); // 把layer放到数组中 return route;};
将该app.get()中的函数存储到route的stack中。(留神源码中也是通过遍历method的形式将get挂载到route的prototype上)
Route.prototype.get = function(){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; // …… // 给route增加layer,这个层中须要寄存办法名和handler var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; };
留神:上述代码均删除了源码中一些异样判断逻辑,不便读者看清整体框架。
通过上述的剖析,能够看出初始化阶段次要做了两件事件:
- 将路由解决形式(app.get()、app.post()……)、app.use()等划分为路由零碎中的一个Layer。
- 对于每一个层中的处理函数全副存储至Route对象中,一个Route对象与一个Layer互相映射。
3.2 申请解决阶段
当服务启动后即进入监听状态,期待申请达到后进行解决。
app.listen()使服务进入监听状态(本质上是调用了http模块)
app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments);};
当连贯建设会调用app实例,app实例中会立刻执行app.handle()函数,app.handle()函数会立刻调用路由零碎的处理函数router.handle()
app.handle = function handle(req, res, callback) { var router = this._router; // 如果路由零碎中解决不了这个申请,就调用done办法 var done = callback || finalhandler(req, res, { env: this.get('env'), onerror: logerror.bind(this) }); //…… router.handle(req, res, done);};
router.handle()次要是依据门路获取是否有匹配的layer,当匹配到之后则调用layer.prototype.handle_request()去执行route中内容的解决
router.prototype.handle = function handle(req, res, out) { // 这个中央参数out就是done,当所有都匹配不到,就从路由零碎中进去,名字很形象 var self = this; // …… var stack = self.stack; // …… next(); function next(err) { // …… // get pathname of request var path = getPathname(req); // find next matching layer var layer; var match; var route; while (match !== true && idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; // …… } // no match if (match !== true) { return done(layerError); } // …… // Capture one-time layer values req.params = self.mergeParams ? mergeParams(layer.params, parentParams) : layer.params; var layerPath = layer.path; // this should be done for the layer self.process_params(layer, paramcalled, req, res, function (err) { if (err) { return next(layerError || err); } if (route) { return layer.handle_request(req, res, next); } trim_prefix(layer, layerError, layerPath, path); }); } function trim_prefix(layer, layerError, layerPath, path) { // …… if (layerError) { layer.handle_error(layerError, req, res, next); } else { layer.handle_request(req, res, next); } }};
layer.handle_request()会调用route.dispatch()触发route中内容的执行
Layer.prototype.handle_request = function handle(req, res, next) { var fn = this.handle; if (fn.length > 3) { // not a standard request handler return next(); } try { fn(req, res, next); } catch (err) { next(err); }};
route中的通过判断申请的办法和route中layer的办法是否匹配,匹配的话则执行相应函数,若所有route中的layer都不匹配,则调到外层的layer中继续执行。
Route.prototype.dispatch = function dispatch(req, res, done) { var idx = 0; var stack = this.stack; if (stack.length === 0) { return done(); } var method = req.method.toLowerCase(); // …… next(); // 此next办法是用户调用的next,如果调用next会执行内层的next办法,如果没有匹配到会调用外层的next办法 function next(err) { // …… var layer = stack[idx++]; if (!layer) { return done(err); } if (layer.method && layer.method !== method) { return next(err); } // 如果以后route中的layer的办法匹配到了,执行此layer上的handler if (err) { layer.handle_error(err, req, res, next); } else { layer.handle_request(req, res, next); } }};
通过上述的剖析,能够看出初始化阶段次要做了两件事件:
- 首先判断layer中的path和申请的path是否统一,统一则会进入route进行解决,否则调到下一层layer
- 在route中会判断route中的layer与申请办法是否统一,统一的话则函数执行,否则不执行,所有route中的layer执行完后跳到上层的layer进行执行。
1.如果感觉这篇文章还不错,来个分享、点赞、吧,让更多的人也看到
2.关注公众号执鸢者,支付学习材料,定期为你推送原创深度好文
欢送大家关注公众号(回复“书籍”获取大量前端学习材料,回复“前端视频”获取大量前端教学视频)