nodejsexpressuse传值_再也不怕面试官问你express和koa的区别了

nodejsexpressuse传值_再也不怕面试官问你express和koa的区别了

2023年7月7日发(作者:)

nodejsexpressuse传值_再也不怕⾯试官问你express和koa的区别了前⾔⽤了那么多年的,终于有时间来深⼊学习express,然后顺便再和koa2的实现⽅式对⽐⼀下。⽼实说,还没看源码之前,⼀直觉得还是很不错的,⽆论从api设计,还是使⽤上都是可以的。但是这次阅读完express代码之后,我可能改变想法了。虽然有着精妙的中间件设计,但是以当前js标准来说,这种精妙的设计在现在可以说是太复杂。⾥⾯的层层回调和递归,不花⼀定的时间还真的很难读懂。⽽koa2的代码呢?简直可以⽤四个字评论:精简彪悍!仅仅⼏个⽂件,⽤上最新的js标准,就很好实现了中间件,代码读起来⼀⽬了然。⽼规矩,读懂这篇⽂章,我们依然有⼀个简单的demo来演⽰: express-vs-koa1、express⽤法和koa⽤法简单展⽰如果你使⽤启动⼀个简单的服务器,那么基本写法应该是这样:const express = require('express')const app = express()const router = ()(async (req, res, next) => { ('I am the first middleware') next() ('first middleware end calling')})((req, res, next) => { ('I am the second middleware') next() ('second middleware end calling')})('/api/test1', async(req, res, next) => { ('I am the router middleware => /api/test1') (200).send('hello')})('/api/testerror', (req, res, next) => { ('I am the router middleware => /api/testerror') throw new Error('I am error.')})('/', router)(async(err, req, res, next) => { if (err) { ('last middleware catch error', err) (500).send('server Error') return } ('I am the last middleware') next() ('last middleware end calling')})(3000)('server listening at port 3000')换算成等价的koa2,那么⽤法是这样的:const koa = require('koa')const Router = require('koa-router')const app = new koa()const router = Router()(async(ctx, next) => { ('I am the first middleware') await next() ('first middleware end calling')})(async (ctx, next) => { ('I am the second middleware') await next() ('second middleware end calling')})('/api/test1', async(ctx, next) => { ('I am the router middleware => /api/test1') = 'hello'})('/api/testerror', async(ctx, next) => { throw new Error('I am error.')})(())(3000)('server listening at port 3000')如果你还感兴趣原⽣nodejs启动服务器是怎么使⽤的,可以参考demo中的这个⽂件:于是⼆者的使⽤区别通过表格展⽰如下(知乎不⽀持markdown也是醉了~表格只能截图了~):上表展⽰了⼆者的使⽤区别,从初始化就看出koa语法都是⽤的新标准。在挂载路由中间件上也有⼀定的差异性,这是因为⼆者内部实现机制的不同。其他都是⼤同⼩异的了。那么接下去,我们的重点便是放在⼆者的中间件的实现上。2、中间件实现原理我们先来看⼀个demo,展⽰了的中间件在处理某些问题上的弱势。demo代码如下:const express = require('express')const app = express()const sleep = (mseconds) => new Promise((resolve) => setTimeout(() => { ('') resolve()}, mseconds))(async (req, res, next) => { ('I am the first middleware') const startTime = () (`================ start ${} ${}`, { query: , body: }); next() const cost = () - startTime (`================ end ${} ${} ${Code} - ${cost} ms`)})((req, res, next) => { ('I am the second middleware') next() ('second middleware end calling')})('/api/test1', async(req, res, next) => { ('I am the router middleware => /api/test1') await sleep(2000) (200).send('hello')})(async(err, req, res, next) => { if (err) { ('last middleware catch error', err) (500).send('server Error') return } ('I am the last middleware') await sleep(2000) next() ('last middleware end calling')})(3000)('server listening at port 3000')该demo中当请求/api/test1的时候打印结果是什么呢?I am the first middleware================ start GET /api/test1I am the second middlewareI am the router middleware => /api/test1second middleware end calling================ end GET /api/test1 200 - 3 如果你清楚这个打印结果的原因,想必对的中间件实现有⼀定的了解。我们先看看第⼀节demo的打印结果是:I am the first middlewareI am the second middlewareI am the router middleware => /api/test1second middleware end callingfirst middleware end calling这个打印符合⼤家的期望,但是为什么刚才的demo打印的结果就不符合期望了呢?⼆者唯⼀的区别就是第⼆个demo加了异步处理。有了异步处理,整个过程就乱掉了。因为我们期望的执⾏流程是这样的:I am the first middleware================ start GET /api/test1I am the second middlewareI am the router middleware => /api/second middleware end calling================ end GET /api/test1 200 - 3 ms那么是什么导致这样的结果呢?我们在接下去的分析中可以得到答案。2.1、express挂载中间件的⽅式要理解其实现,我们得先知道到底有多少种⽅式可以挂载中间件进去?熟悉的童鞋知道吗?知道的童鞋可以⼼⾥默默列举⼀下。⽬前可以挂载中间件进去的有:(HTTP Method指代那些http请求⽅法,诸如Get/Post/Put等等).[HTTP Method]outer.[HTTP Method]2.2、express中间件初始化express代码中依赖于⼏个变量(实例):app、router、layer、route,这⼏个实例之间的关系决定了中间件初始化后形成⼀个数据模型,画了下⾯⼀张图⽚来展⽰:图中存在两块Layer实例,挂载的地⽅也不⼀样,以为例⼦,我们通过调试找到更加形象的例⼦:结合⼆者,我们来聊聊express中间件初始化。为了⽅便,我们把上图1叫做初始化模型图,上图2叫做初始化实例图看上⾯两张图,我们抛出下⾯⼏个问题,搞懂问题便是搞懂了初始化。初始化模型图Layer实例为什么分两种?初始化模型图Layer实例中route字段什么时候会存在?初始化实例图中挂载的中间件为什么有7个?初始化实例图中圈2和圈3的route字段不⼀样,⽽且name也不⼀样,为什么?初始化实例图中的圈4⾥也有Layer实例,这个时候的Layer实例和上⾯的Layer实例不⼀样吗?⾸先我们先输出这样的⼀个概念:Layer实例是path和handle互相映射的实体,每⼀个Layer便是⼀个中间件。这样的话,我们的中间件中就有可能嵌套中间件,那么对待这种情形,express就在Layer中做⼿脚。我们分两种情况挂载中间件:使⽤、来挂载的经过⼀系列处理之后最终也是调⽤的使⽤、app.[Http Method]、、、router.[Http Method]、来挂载的、app.[Http Method]、、、router.[Http Method]经过⼀系列处理之后最终也是调⽤的因此我们把焦点聚焦在和这两个⽅法。2.2.1、该⽅法的最核⼼⼀段代码是:for (var i = 0; i < ; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') { throw new TypeError('() requires a middleware function but got a ' + gettype(fn)) } // add the middleware debug('use %o %s', path, || '') var layer = new Layer(path, { sensitive: nsitive, strict: false, end: false }, fn); // 注意这个route字段设置为undefined = undefined; (layer);}此时⽣成的Layer实例对应的便是初始化模型图1指⽰的多个Layer实例初始化模型图1指⽰的多个Layer实例,此时以为例⼦,我们看初始化实例图圈1的所有初始化实例图圈1的所有Layer实例,会发现除了我们⾃定义的中间件(共5个),还有两个系统⾃带的,看初始化实例图的Layer的名字分别Layer实例是:query和expressInit。⼆者的初始化是在[]中的lazyrouter⽅法:uter = function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: d('case sensitive routing'), strict: d('strict routing') }); this._(query(('query parser fn'))); // 最终调⽤的就是⽅法 this._((this)); // 最终调⽤的就是⽅法 }};于是回答了我们刚才的第三个问题。7个中间件,2个系统⾃带、3个APP级别的中间、2个路由级别的中间件2.2.2、我们说过、app.[Http Method]、、、router.[Http Method]经过⼀系列处理之后最终也是调⽤的,所以我们在demo中的,使⽤了两次,其最后调⽤了,我们看该⽅法核⼼实现: = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: nsitive, strict: , end: true }, (route)); = route; (layer); return route;};这么简单的实现,与上⼀个⽅法的实现唯⼀的区别就是多了new Route这个。通过⼆者对⽐,我们可以回答上⾯的好⼏个问题:初始化模型图Layer实例为什么分两种? 因为调⽤⽅式的不同决定了Layer实例的不同,第⼆种Layer实例是挂载在route实例之下的。初始化模型图Layer实例中route字段什么时候会存在?使⽤的时候就会存在初始化实例图中圈2和圈3的route字段不⼀样,⽽且name也不⼀样,为什么?圈2的Layer因为我们使⽤箭头函数,不存在函数名,所以name是anonymous,但是圈3因为使⽤的,所以其统⼀的回调函数都是h,因此其函数名字都统⼀是bounddispatch,同时⼆者的route字段是否赋值也⼀⽬了然最后⼀个问题,既然实例化route之后,route有了⾃⼰的Layer,那么它的初始化⼜是在哪⾥的?初始化核⼼代码:// router//ype[method]for (var i = 0; i < ; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = (handle); var msg = 'Route.' + method + '() requires a callback function but got a ' + type throw new Error(msg); } debug('%s %o', method, ) var layer = Layer('/', {}, handle); = method; s[method] = true; (layer); }可以看到新建的route实例,维护的是⼀个path,对应多个method的handle的映射。每⼀个method对应的handle都是⼀个layer,path统⼀为/。这样就轻松回答了最后⼀个问题了。⾄此,再回去看初始化模型图,相信⼤家可以有所明⽩了吧~2.3、express中间件的执⾏逻辑整个中间件的执⾏逻辑⽆论是外层Layer,还是route实例的Layer,都是采⽤递归调⽤形式,⼀个⾮常重要的函数next()实现了这⼀切,这⾥做了⼀张流程图,希望对你理解这个有点⽤处:我们再把的代码使⽤另外⼀种形式实现,这样你就可以完全搞懂整个流程了。为了简化,我们把系统挂载的两个默认中间件去掉,把路由中间件去掉⼀个,最终的效果是:((req, res) => { ('I am the first middleware'); ((req, res) => { ('I am the second middleware'); (async(req, res) => { ('I am the router middleware => /api/test1'); await sleep(2000) (200).send('hello') })(req, res) ('second middleware end calling'); })(req, res) ('first middleware end calling')})(req, res)因为没有对await或者promise的任何处理,所以当中间件存在异步函数的时候,因为整个next的设计原因,并不会等待这个异步函数resolve,于是我们就看到了sleep函数的打印被放在了最后⾯,并且第⼀个中间件想要记录的请求时间也变得不再准确了~但是有⼀点需要申明的是虽然打印变得奇怪,但是绝对不会影响整个请求,因为response是在我们await之后,所以请求是否结束还是取决于我们是否调⽤了这类函数⾄此,希望整个express中间件的执⾏流程你可以熟悉⼀⼆,更多细节建议看看源码,这种精妙的设计确实不是这篇⽂章能够说清楚的。本⽂只是想你在⾯试的过程中可以做到有话要说~接下去,我们分析⽜逼的Koa2,这个就不需要费那么⼤篇幅去讲,因为实在是太太容易理解了。3、koa2中间件koa2中间件的主处理逻辑放在了koa-compose,也就是仅仅⼀个函数的事情:function compose (middleware) { if (!y(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return (new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === ) fn = next if (!fn) return e() try { return e(fn(context, (null, i + 1))); } catch (err) { return (err) } } }}每个中间件调⽤的next()其实就是这个:(null, i + 1)还是利⽤闭包和递归的性质,⼀个个执⾏,并且每次执⾏都是返回promise,所以最后得到的打印结果也是如我们所愿。那么路由的中间件是否调⽤就不是koa2管的,这个⼯作就交给了koa-router,这样koa2才可以保持精简彪悍的风格。再贴出koa中间件的执⾏流程吧:最后有了这篇⽂章,相信你再也不怕⾯试官问你express和koa的区别了~参考1. koa2. express3. http

发布者:admin,转转请注明出处:http://www.yc00.com/news/1688683088a162175.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信