当前位置: 首页 > 后端技术 > Node.js

Express源码三步解析

时间:2023-04-03 19:18:40 Node.js

关注公众号“风筝”,获取海量教学视频和私密总结面筋,进专业交流群。抖音上有幸看到一位程序员讲解如何阅读源码,主要分为三个步骤:领悟思想、把握设计、体验细节。领悟思想:只需要理解作者设计框架的初衷和目的把握设计:只需要理解代码的接口和抽象类以及宏观设计经验细节:基于顶层抽象接口设计,并根据以上三步法逐步展开代码的图片,迫不及待地进行了Express手术。如果这个源码分析有什么不对的地方,读者可以在下方留言,我们一起交流。一、领悟思想Express中文网介绍了Express是基于Node.js平台的快速、开放、极简的Web开发框架。在这句话中,可以解读以下意思:Express基于Node.js平台,具有快速、极简的特点,表明其初衷是通过扩展Node.js的功能来提高开发效率。框架的开放性意味着框架不会对开发者施加过多的限制,可以自由发挥自己的想象力来扩展功能。Express是一个web开发框架,说明作者的定位是帮助我们更方便的处理HTTP请求和响应。2.把握设计在了解了作者的设计思路后,下面从源码目录、核心设计原则、抽象接口等方面对Express进行整体把握。2.1源码目录下面是Express的源码目录,比较简单。├─application.js---创建Express应用后可以直接调用的api都在这里(核心)
├─express.js---入口文件,创建Express应用
├─request.js---丰富http中request实例的功能
├─response.js---丰富http中response实例的功能
├─utils.js---toolfunction
├─view.js---模板渲染相关内容
├─router---路由相关内容(核心)
|├─index.js
|├─layer.js
|└route.js
├─中间件---中间件相关内容
|├─init.js---将新增的request和response函数挂载在原request的request和response的原型上
|└query.js---在request的query属性中加入requesturl中的query部分
2.2抽象接口对源码的目录结构有了一定的了解,下面用UML类图来进一步了解系统各模块的依赖关系,为后续的源码分析打下坚实的基础。2.3设计原则这部分是整个Express框架的核心。下图展示了整个框架的运行过程。看看它是不是很混乱。为了理解这部分,需要弄清楚四个概念:Application、Router、Layer、Route。为了理清以上四个概念,先介绍一段代码constexpress=require('./express');constres=require('./response');constapp=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',...)都可以成为一个LayerRoute,一个Layer中会有多个处理函数。这多个处理功能构成了一个Route,Route中的每一个function成为Route中的Layer。对于上面的代码,app.get('/test1',...)中的两个函数组成了一个Route,每个函数都是Route中的一个Layer。理解了以上概念后,再结合这张图,大概可以对整个过程有一个直观的体验。先启动服务,然后客户端发起请求http://localhost:3000/test2,流程应该如何运行?当服务启动时,程序会依次执行,并存储路由系统中的路径、请求方法、处理函数(这些信息会按照一定的结构存储在Router、Layer、Route中),相应的地址会被监听,等待请求到达。当请求到达时,首先根据请求的路径从上到下进行匹配。如果路径匹配正确,则进入该层,否则跳出该层。如果Layer匹配,则匹配请求方法,如果匹配方法正确,则执行对应Route中的函数。上面的解释比较简单,后面的细节部分会做进一步的阐述。3、体验细节通过以上对Express设计原理的分析,将从两个方面进一步解读源码。下面的流程图是一个普通的Express项目的流程。首先会初始化app实例,然后会调用一系列的中间件,最后创建一个监听器。对于整个项目的运行,主要分为两个阶段:初始化阶段和请求处理阶段。下面将以app.get()为例来说明核心细节。3.1初始化阶段让我们使用app.get()路由来了解项目的初始化阶段。先看app.get()的内容(源码中的app.get()是遍历方法生成的)app.get=function(path){//...this.lazyrouter();varroute=this._router.route(path);route.get.apply(route,slice.call(arguments,1));返回这个;};在app.lazyrouter()中会完成路由器实例化过程严格:this.enabled('严格路由')});//this这里会用到一些中间件this._router.use(query(this.get('queryparserfn')));this._router.use(middleware.init(this));}};注意:在这个过程中,实际上它使用了单例模式来保证整个过程中获取路由器实例的唯一性。调用router.route()方法完成图层的实例化、处理和保存,并返回实例化的路由。(注意源码是proto.route)router.prototype.route=functionroute(path){varroute=newRoute(path);varlayer=newLayer(path,{sensitive:this.caseSensitive,strict:this.strict,end:true},route.dispatch.bind(route));layer.route=route;//把路由放在图层上this.stack.push(layer);//将图层放入数组returnroute;};将app.get()中的函数存储到路由的堆栈中。(注意源码也是通过遍历方法挂载get到路由的原型)Route.prototype.get=function(){varhandles=flatten(slice.call(arguments));for(vari=0;i3){//不是标准的请求处理程序returnnext();}try{fn(req,res,next);}赶上(犯错){下一个(犯错);}};路由中的方法判断请求的方法是否与路由中层的方法匹配,如果匹配则执行相应的函数,如果路由中所有方法层都不匹配则转到外层继续执行Route.prototype.dispatch=functiondispatch(req,res,done){varidx=0;varstack=this.stack;if(stack.length===0){returndone();}var方法=要求。方法.toLowerCase();//...下一个();//这个next方法由用户调用。如果调用next,就会执行内层的next方法。如果没有匹配到,就会调用外层的next方法。functionnext(err){//...varlayer=stack[idx++];如果(!层){返回完成(错误);}if(layer.method&&layer.method!==method){returnnext(err);}//如果当前路由中layer的方法匹配,则在该层执行handlerif(err){layer.handle_error(err,req,res,next);}else{layer.handle_request(req,res,next);}}};通过上面的分析可以看出,初始化阶段主要做了两件事:首先判断层中的路径是否与请求的路径一致,一致则进入路由进行处理,否则转入下一层layer路由中,判断路由中的层是否与请求方法一致。如果一致则执行函数,否则不执行。路由中的所有层都执行完后,会跳转到下层执行。1.如果觉得这篇文章还不错,请分享,点赞,让更多人看到2.关注公众号致远,获取学习资料,定期为您发送原创深度文章。欢迎大家关注公众号(回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频)