当前位置: 首页 > 科技观察

手写Express核心原理,再也不怕面试官问我Express原理

时间:2023-03-17 21:24:21 科技观察

转载请联系前方阳光公众号。1.首先安装express2.创建example.js文件创建myExpress.js文件实现app.get()方法实现post等方法实现app.all方法实现中间件app.use什么是错误中间件?学习总结1、首先安装expressnpminstallexpress安装express,用于演示。代码已经放在github上:https://github.com/Sunny-lucking/HowToBuildMyExpress。你能给我一颗星吗?感谢你们。2.创建example.js文件//example.jsconstexpress=require('express')constapp=express()constport=3000app.get('/',(req,res)=>{res.send('HelloWorld!')})app.listen(port,()=>{console.log(`Exampleapplisteningathttp://localhost:${port}`)})如代码所示,执行nodeexample.js运行一个服务器。如下图所示,现在我们决定创建一个属于我们的快递文件,导入的快递改为导入我们手写的快递。好的,让我们现在来实现我们的express!创建myExpress.js文件constexpress=require('express')constapp=express()从这两行代码我们可以知道express获取的是一个方法,然后这个方法执行完之后就获取到了app。而app其实就是一个函数。至于为什么是函数,下面我们来揭秘。我们可以初步实现express如下://myExpress.jsfunctioncreateApplication(){letapp=function(req,res){}returnapp;}module.exports=createApplication;在上面的代码中,发现app有一个listen方法。所以我们可以进一步在app中添加listen方法:listen实现最重要的是创建服务器,并将服务器绑定到某个端口运行。因此,可以通过这种方式改进listen方法。//myExpress.jslethttp=require('http');functioncreateApplication(){letapp=function(req,res){res.end('hahha');}app.listen=function(){letserver=http.createServer(app)server.listen(...arguments);}returnapp;}module.exports=createApplication;这里可能有同学会有疑惑,这里为什么要把http.createServer(app)传给app。其实我们不传入app,也就是说做app不是方法,也是可以的。我们可以改成这样。//myExpress.jslethttp=require('http');functioncreateApplication(){letapp={};app.listen=function(){letserver=http.createServer(function(req,res){res.end('hahha')})server.listen(...arguments);}returnapp;}module.exports=createApplication;如代码所示,我们把app改成对象是没有问题的。实施app.get()方法app.get方法接受两个参数,路径和回调函数。//myExpress.jslethttp=require('http');functioncreateApplication(){letapp={};app.routes=[]app.get=function(path,handler){letlayer={method:'get',path,handler}app.routes.push(layer)}app.listen=function(){letserver=http.createServer(function(req,res){res.end('hahha')})server.listen(...参数);}returnapp;}module.exports=createApplication;如上代码所示,在app中添加了一个route对象,在执行get方法时,将接收到的两个参数path和method封装成一个对象推到路线中。可以想象,当我们在浏览器中输入路径时,http.createServer中的回调函数肯定会被执行。所以,我们需要在这里获取浏览器的请求路径。解析得到路径。然后遍历循环路由,找到对应的路由,执行回调方法。如下代码所示。//myExpress.jslethttp=require('http');consturl=require('url');functioncreateApplication(){letapp={};app.routes=[]app.get=function(path,handler){letlayer={method:'get',path,handler}app.routes.push(layer)}app.listen=function(){letserver=http.createServer(function(req,res){//取出层//1。getRequestmethodletm=req.method.toLocaleLowerCase();let{pathname}=url.parse(req.url,true);//2.找到对应的路由并执行回调方法for(leti=0;i{method=method.toLocaleLowerCase()app[method]=function(path,handler){letlayer={method,path,handler}app.routes.push(layer)}});...}module.exports=createApplication;如代码所示,http.METHODS是一组方法。像这样的数组:["GET","POST","DELETE","PUT"]。通过遍历方法数组,可以实现所有的方法。测试跑了一下,确实成功了。实现app.all方法all表示匹配所有方法,app.all('/user')表示匹配路径为/user的所有路由app.all('*')表示匹配任意路由任意方法实现all方法为也很简单,如下代码所示:app.all=function(path,handler){letlayer={method:"all",path,handler}app.routes.push(layer)}然后只需要继续修改路由器匹配逻辑,如下代码所示,只需要修改判断即可。app.listen=function(){letserver=http.createServer(function(req,res){//取出层//1.获取请求的方法letm=req.method.toLocaleLowerCase();let{pathname}=url.parse(req.url,true);//2.找到对应的路由并执行回调方法for(leti=0;i{method=method.toLocaleLowerCase()app[method]=function(path,handler){letlayer={method,path,handler}app.routes.push(layer)}});app.listen=function(){letserver=http.createServer(function(req,res){//移除层//1.获取请求的方法letm=req.method.toLocaleLowerCase();let{pathname}=url.parse(req.url,true);//2.找到对应的路由并执行回调方法functionnext(){//整个数组已经迭代,但是没有找到匹配的路径if(index===app.routes.length)returnres.end('Cannotfind')let{method,path,handler}=app.routes[index++]//每次调用接下来,进入下一层if(方法==='middle'){//处理中间件if(path==='/'||path===pathname||pathname.starWidth(path+'/')){handler(req,res,next)}else{//继续遍历next();}}else{//处理路由if((method===m||method==='all')&&(path===pathname||path==="*")){handler(req,res);}else{next();}}}next()res.end('hahha')})server.listen(...arguments);}returnapp;}module.exports=createApplication;当我们请求path时,你会发现中间件确实执行成功了。但是,这里中间价的实现并不完美。因为,我们在使用中间件的时候,是不需要传递路由的。比如:app.use((req,res)=>{console.log("路由我没有中间价");})这个也是可以的,那么怎么实现其实很简单,我们判断下如果没有传递路径就好了,如果没有就给一个默认路径“/”,实现代码如下:app.use=function(path,handler){if(typeofpath!=="string"){//第一个参数不是字符串,说明不是路径,而是方法handler=path;path="/"}letlayer={method:"middle",path,handler}app.routes。push(layer)}看,是不是很聪明,很简单。我们试图访问路径“/middle”嗯?第一个中间件没有执行,为什么?对了,使用中间件的时候,next()必须在最后执行完,才能交给下一个中间件或者路由执行。当我们请求“/middle”路径时,可以看到请求确??实成功了,中间件也执行成功了。说明我们的逻辑是正确的。其实中间件已经完成了,但是别忘了,还有一个报错中间件?什么是错误中间件?错误处理中间件函数的定义与其他中间件函数基本相同。区别在于错误处理函数有四个参数而不是三个,特别是带有签名(err、req、res、next):app.use(function(err,req,res,next){console.error(err.stack));res.status(500).send('Somethingbroke!');});当我们在执行next()方法时,如果抛出错误,我们会直接寻找错误的中间件执行,而不是执行其他中间件文件或路由。例如:如图,当第一个中间件给next传递参数时,说明执行出错了。然后会跳过其他路游和中间件以及路由,直接执行出错的中间件。当然,在执行完错误中间件之后,还会继续执行下面的中间件。例如:如图所示,会执行error中间件的后者。如何落实原则?很简单,直接看代码解释,在next中多加一层判断即可:routes.length)returnres.end('Cannotfind')let{method,path,handler}=app.routes[index++]//每次调用next时,转到下一层if(err){//如果有错误,应该寻找中间件执行。if(handler.length===4){//查找错误中间件handler(err,req,res,next)}else{//继续徐州next(err)}}else{if(method==='middle'){//处理中间件if(path==='/'||path===pathname||pathname.starWidth(path+'/')){handler(req,res,next)}else{//继续traversenext();}}else{//处理路由if((method===m||method==='all')&&(path===pathname||path==="*")){handler(req,res);}else{next();}}}}看代码可以看到next中是否有err的值,然后判断是否需要找error中间件执行。如图,请求/middle路径,执行成功。至此,express框架的实现就完成了。学习总结通过这次express手写原理的实现,对express的使用有了更深的理解,发现:中间件和路由被压入了一个routes数组。执行中间件时,传递next以便可以执行下一个中间件或路由。路由执行的时候不会通过next,这也使得路由的遍历提前结束。error中间件执行完之后,后面的中间件或者路由还是会执行。