打算一步步教你如何实现koa-router,因为讲解太多,所以我就简化成一个mini版首先从实现一些功能到阅读源码,希望大家能更好的理解。我希望你之前已经阅读过koa源代码。如果没有,我给你一个最核心的需求——路由匹配的链接。路由器最重要的是路由匹配。先从最核心的路由器说起。)=>{ctx.body='koa2string'})router.get('/json',async(ctx,next)=>{ctx.body='koa2json'})我们想要访问/string的路径页面显示'koa2string'pathaccess/json页面显示'koa2json'首先分析收集开发者输入的信息配置1.我们需要一个数组,每个数组都是一个对象,每个对象包含路径,方法,函数,pass我们将这个参数信息数组命名为stackconststack=[]2.对于每一个对象,我们将其命名为layer,并将其定义为一个函数functionLayer(){}我们将页面比作一个盒子,盒子是外部的,盒子需要有一个入口,需要包含。将每个路由器比作一个盒子里的一个对象,对象是两个js页面的内部定义,以router.js作为入口。对于访问当前页面的处理,layer.js包含了开发者在router上约定的规则.jsmodule.exports=Router;functionRouter(opts){//容纳layerlayerthis.stack=[];};layer.jsmodule.exports=Layer;functionLayer(){};我们需要在Router里面放很多方法,我们可以在Router里面挂载方法,或者在原型上挂载函数,但是Router有可能会被多次实例化,所以必须在里面开辟一个新的空间,并且同样的空间安装在原型上。最终决定在原型上挂载的方法有很多,我们先实现几个常用的constmethods=['get','post','put','head','delete','options',];methods.forEach(function(method){Router.prototype[method]=function(path,middleware){//对于path,middleware,我们需要交给layer,并得到layer返回的结果//这里要实现另外一个函数,我们称之为register,表示暂存this.register(path,[method],middleware);//因为get可以继续get,所以我们returnthisreturnthis};});实现层通信Router.prototype.register=function(path,methods,middleware){letstack=this.stack;letroute=newLayer(path,methods,middleware);stack.push(路线);回程};这里我们先来写layerconstpathToRegExp=require('path-to-regexp');functionLayer(path,methods,middleware){//把方法名放在方法数组中this.methods=[];//堆栈保存中间件Functionthis.stack=Array.isArray(middleware)?中间件:[中间件];//路径this.path=path;//为这个路径生成匹配规则,这里是第三方的this.regexp=pathToRegExp(path);//methodsmethods.forEach(function(method){this.methods.push(method.toUpperCase());//绑定层的this,否则匿名函数的this指向窗口},this);};//原型方法返回truematchLayer.prototype.match=function(path){returnthis.regexp.test(path);};回到路由器层定义匹配方法。根据Developer传入的路径,方法返回一个对象(包括是否匹配,匹配成功的层,匹配成功的方法)Router.prototype.match=function(path,method){const层数=this.stack;让图层;constmatched={path:[],pathAndMethod:[],route:false};//循环注册的堆栈层的每一层(varlen=layers.length,i=0;i{ctx.body='HelloWorld!';}).post('/users',(ctx,next)=>{//...}).put('/users/:id',(ctx,next)=>{//...}).del('/users/:id',(ctx,next)=>{//...}).all('/users/:id',(ctx,next)=>{//...});写法的多样性是为了方便很多开发者使用router.get('user','/users/:id',(ctx,next)=>{//...});router.url('user',3);如下写法都是一条路径//=>"/users/3"支持中间件router.get('/users/:id',(ctx,next)=>{returnUser.findOne(ctx.params.id).then(function(user)ctx.user=user;next();});},ctx=>{console.log(ctx.user);//=>{id:17,name:"Alex"}})多层次套组varforums=newRouter();varposts=newRouter();posts.get('/',(ctx,next)=>{...});posts.get('/:pid',(ctx,next)=>{...});forums.use('/forums/:fid/posts',posts.routes(),posts.allowedMethods());//响应“/forums/123/posts”和“/forums/123/posts/123”app.use(forums.routes());路径前馈(Routerprefixes)varrouter=newRouter({prefix:'/users'});router.get('/',...);//响应“/users”router.get('/:id',...);//响应“/users/:id”URL参数router.get('/:category/:title',(ctx,next)=>{console.log(ctx.params);//=>{category:'编程',title:'how-to-node'}});router.jsmethods.forEach(function(method){Router.prototype[method]=function(name,path,middleware){varmiddleware;if(typeofpath==='string'||pathinstanceofRegExp){//line第二个参数是否为路径,如果是路径字符串,则为下表中的中间件[2]middleware=Array.prototype.slice.call(arguments,2);}else{middleware=Array.prototype.slice.call(arguments,1);path=name;name=null;}this.register(path,[method],middleware,{name:name});returnthis;};});//aliasRouter。原型。del=Router.prototype['delete'];方法参考第三方包含functiongetBasicNodeMethods(){return['get','post','put','head','delete','options','trace','复制','锁定','mkcol','移动','清除','propfind','proppatch','解锁','报告','mkactivity','结帐','合并','m-search','notify','subscribe','unsubscribe','patch','search','connect'];}Router.prototype.routes=Router.prototype.middleware=function(){varrouter=this;vardispatch=functiondispatch(ctx,next){varpath=router.opts.routerPath||ctx.routerPath||ctx.路径;varmatched=router.match(path,ctx.method);变种层链,层,我;if(ctx.matched){ctx.matched.push.apply(ctx.matched,matched.path);}else{ctx.matched=matched.path;}//ctx挂载路由器ctx.router=router;如果(!matched.route)返回next();//获取路径和方法都匹配的层varmatchedLayers=matched.pathAndMethod//取出最后一层varmostSpecificLayer=matchedLayers[matchedLayers.length-1]//挂载_matchedRoute属性ctx._matchedRoute=mostSpecificLayer.path;//如果有名字,下面会有一个名字,名字是一个字符串//router.get('/string','/string/:1',async(ctx,next)=>{//ctx.body='koa2string'//})if(mostSpecificLayer.name){//挂上_matchedRouteName属性ctx._matchedRouteName=mostSpecific图层名称;}//layerChain是一个中间件数组,目前有两个函数.captures);ctx.params=layer.params(path,ctx.captures,ctx.params);//console.log('captures2',ctx.captures)//ctx.captures是:id的捕获,正则匹配切片被拦截得到//ctx.params是一个对象{id:1}ctx.routerName=layer.name;returnnext();});returnmemo.concat(layer.stack);},[]);//中间件调用layerChainreturncompose(layerChain)(ctx,next);};//routes挂载路由器对象dispatch.router=this;//每次调用routes都会返回一个dispatch函数(layer.stack和memo),该函数在该路径下还有一个router属性对象returndispatch;};这里使用compose-koa中间件来处理多个函数的传递和多个匹配的捕获和参数。自定义路径参数param的实现如下要求,访问/users/:1可以得到param中的userrouter.param('user',(user,ctx,next)=>{ctx.user=user;if(!ctx.user)returnctx.status=404;returnnext();}).get('/users/:user',ctx=>{ctx.body=ctx.user;})Router.prototype.param=function(param,middleware){this.params[param]=middleware;this.stack.forEach(function(route){route.param(param,middleware);});returnthis;};Layer.prototype.param=function(param,fn){varstack=this.stack;varparams=this.paramNames;varmiddleware=function(ctx,next){//第一个参数是ctx.params[param],params拿到了userreturnfn.call(this,ctx.params[param],ctx,next);};};params实际如下需要router.get('/:category/:title',(ctx,next)=>{console.log(ctx.params);//=>{category:'programming',title:'how-to-node'}});例子router.get('/string/:id',async(ctx,next)=>{ctx.body='koa2string'})访问string/1//拿到{id:1}ctx.params=layer.params(path,ctx.captures,ctx.params);Layer.prototype.params=function(path,captures,existingParams){varparams=existingParams||{};对于(varlen=captures.length,i=0;i