?之前在使用express的时候从来没有想过为什么可以这样写,中间件可以这样用。今天决定把中间件原理给写一遍。不多cc,直接上代码。
在like-express文件中
/*简单的实现中间件原理
思路:
定义一个类,类里面有和express对应的use get post函数,
使用的时候,创建实例,并使用这些函数。将这些函数里面的参数,如app.use('/',f,f),进行解析,
全部存入到对象的对应属性(这些属性应该都为对象数组,每个对象为path和stackk属性组成)中
在http服务中会对用户输入的接口进行拦截,这时我们对其进行处理,对客户端发过来不同的method和不同的url返回对应要执行的stack(stack存的是函数数组),
最后写一个next核心机制去执行这些函数。
*/const http = require('http')
const slice = Array.prototype.slice //数组原型上的slice(start,end),从已有的数组中返回选定的元素。class LikeExpress{//构造函数constructor(){//存放中间件的列表this.routes = {all:[], //对应app.use();是一个对象数组,每个对象为path和stackk属性组成get:[], //app.get()post:[] //app.post}}//将path和stack放入到info中,stack存的是函数,返回inforegister(path){const info = {}//将path和stack放入到info中,stack存的是函数if(typeof path === 'string'){info.path = path//从第二个参数开始,转换为数组,存入stackinfo.stack = slice.call(arguments,1) //arguments为函数参数数组;slice.call(数组,起始位置,结束位置)}else{info.path = '/'//从第一个参数开始,转换为数组,存入stackinfo.stack = slice.call(arguments,0) //arguments为函数参数数组;slice.call(数组,起始位置,结束位置)}return info}//实例中的use函数,来将用户输入实参存入到对应的routes中all数组,存入的是一个对象,又path,stack属性use(){const info = this.register.apply(this,arguments) //apply改变第一个this为第二个this的指向,arguments为当前函数的参数数组;apply函数必须要有两个参数(新指向,参数数组)this.routes.all.push(info)}get(){const info = this.register.apply(this,arguments) //apply改变this指向为当前类中的thisthis.routes.get.push(info)}post(){const info = this.register.apply(this,arguments) //apply改变this指向为当前类中的thisthis.routes.post.push(info)}//匹配用户使用的use,get,post方法,返回用户输入的对应路由的后端输入函数match(method,url){let stack = []//不处理/favicon.ico请求if(url === '/favicon.ico'){return stack}//获取后端输入的routes,根据method进行筛选let curRoutes = []curRoutes = curRoutes.concat(this.routes.all) //concat数组拼接函数curRoutes = curRoutes.concat(this.routes[method])//遍历筛选后的对象数组,拦截用户输入的路由,返回后端输入的函数curRoutes.forEach(routeInfo =>{if(url.indexOf(routeInfo.path === 0)){ //有bug,如果是get或者post客户端输入'/api/test/111',后端拦截的是'/api/test',依旧返回stack//客户端访问url === '/api/get-cookie' 且 后端拦截的 routeInfo.path === '/'//客户端访问url === '/api/get-cookie' 且 后端拦截的 routeInfo.path === '/api'//客户端访问url === '/api/get-cookie' 且 后端拦截的 routeInfo.path === '/api/get-cookie'stack = stack.concat(routeInfo.stack)}})return stack}//核心的next机制,去执行match后的函数handle(req,res,stack){const next = ()=>{//依次拿到匹配的中间件const middleware = stack.shift() //shift()函数为从数组中取出第一个元素,并将其删除if(middleware){//执行中间件函数middleware(req,res,next)}}next()}//http服务入口文件callback(){return (req,res) =>{//res加入json函数res.json = (data)=>{res.setHeader('Content-type','application/json')res.end(JSON.stringify(data))}const url = req.urlconst method = req.method.toLowerCase()const resultList = this.match(method,url) //返回拦截用户输入的路由,返回的后端输入的函数this.handle(req,res,resultList) //next核心机制,去执行这些函数}}listen(...args){const server = http.createServer(this.callback()) //开启http服务server.listen(...args) //监听端口}}//工厂函数
module.exports = ()=>{return new LikeExpress()
}
在app.js文件中
const express = require('./like-express')//本次http请求的实例
const app = express()app.use((req,res,next)=>{console.log('请求开始...',req.method,req.url)next()
})function loginChech(req,res,next){setTimeout(()=>{console.log('模拟登录成功')next()})
}app.get('/api/get-test',loginChech,(req,res,next)=>{console.log(req.method,'处理路由')res.json({errno:0,msg:"测试成功"})next()
})app.post('/api/post-test',(req,res,next)=>{console.log(req.method,'处理路由')next()
})app.listen(3000,()=>{console.log('server is running on port 3000')
})
?最后在控制台node app启动进程即可,在浏览器或者postman输入接口测试即可