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

2025-05-29 0 60

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

  • 一、首先安装express
  • 二、创建example.js文件
  • 创建myExpress.js文件
  • 实现app.get()方法
  • 实现post等其他方法。
  • 实现app.all方法
  • 中间件app.use的实现
  • 什么是错误中间件?
  • 学习总结

一、首先安装express

  1. npminstallexpress

安装express是为了示范。

已经把代码放到github:https://github.com/Sunny-lucking/HowToBuildMyExpress 。可以顺手给个star吗?谢谢大佬们。

二、创建example.js文件

  1. //example.js
  2. constexpress=require('express')
  3. constapp=express()
  4. constport=3000
  5. app.get('/',(req,res)=>{
  6. res.send('HelloWorld!')
  7. })
  8. app.listen(port,()=>{
  9. console.log(`Exampleapplisteningathttp://localhost:${port}`)
  10. })

如代码所示,执行node example.js就运行起了一个服务器。

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

如下图所示,现在我们决定创建一个属于我们的express文件,引入的express改成引入我们手写的express。。

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

好了,现在开始实现我们的express吧!

创建myExpress.js文件

  1. constexpress=require('express')
  2. constapp=express()

由 这两句代码,我们可以知道,express得到的是一个方法,然后方法执行后得到了app。而app实际上也是一个函数,至于为什么会是函数,我们下面会揭秘。

我们可以初步实现express如下:

  1. //myExpress.js
  2. functioncreateApplication(){
  3. letapp=function(req,res){
  4. }
  5. returnapp;
  6. }
  7. module.exports=createApplication;

在上面代码中,发现app有listen方法。

因此我们可以进一步给app添加listen方法:

  1. //myExpress.js
  2. functioncreateApplication(){
  3. letapp=function(req,res){
  4. }
  5. app.listen=function(){
  6. }
  7. returnapp;
  8. }
  9. module.exports=createApplication;

app.listen实现的是创建一个服务器,并且将服务器绑定到某个端口运行起来。

因此可以这样完善listen方法。

  1. //myExpress.js
  2. lethttp=require('http');
  3. functioncreateApplication(){
  4. letapp=function(req,res){
  5. res.end('hahha');
  6. }
  7. app.listen=function(){
  8. letserver=http.createServer(app)
  9. server.listen(…arguments);
  10. }
  11. returnapp;
  12. }
  13. module.exports=createApplication;

这里可能会有同学有所疑问,为什么 http.createServer(app)这里要传入app。

其实我们不传入app,也就是说,让app不是一个方法,也是可以的。

我们可以改成这样。

  1. //myExpress.js
  2. lethttp=require('http');
  3. functioncreateApplication(){
  4. letapp={};
  5. app.listen=function(){
  6. letserver=http.createServer(function(req,res){
  7. res.end('hahha')
  8. })
  9. server.listen(…arguments);
  10. }
  11. returnapp;
  12. }
  13. module.exports=createApplication;

如代码所示,我们将app改成一个对象,也是没有问题的。

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

实现app.get()方法

app.get方法接受两个参数,路径和回调函数。

  1. //myExpress.js
  2. lethttp=require('http');
  3. functioncreateApplication(){
  4. letapp={};
  5. app.routes=[]
  6. app.get=function(path,handler){
  7. letlayer={
  8. method:'get',
  9. path,
  10. handler
  11. }
  12. app.routes.push(layer)
  13. }
  14. app.listen=function(){
  15. letserver=http.createServer(function(req,res){
  16. res.end('hahha')
  17. })
  18. server.listen(…arguments);
  19. }
  20. returnapp;
  21. }
  22. module.exports=createApplication;

如上面代码所示,给app添加了route对象,然后get方法执行的时候,将接收到的两个参数:路径和方法,包装成一个对象push到routes里了。

可想而知,当我们在浏览器输入路径的时候,肯定会执行http.createServer里的回调函数。

所以,我们需要在这里 获得浏览器的请求路径。解析得到路径.

然后遍历循环routes,寻找对应的路由,执行回调方法。如下面代码所示。

  1. //myExpress.js
  2. lethttp=require('http');
  3. consturl=require('url');
  4. functioncreateApplication(){
  5. letapp={};
  6. app.routes=[]
  7. app.get=function(path,handler){
  8. letlayer={
  9. method:'get',
  10. path,
  11. handler
  12. }
  13. app.routes.push(layer)
  14. }
  15. app.listen=function(){
  16. letserver=http.createServer(function(req,res){
  17. //取出layer
  18. //1.获取请求的方法
  19. letm=req.method.toLocaleLowerCase();
  20. let{pathname}=url.parse(req.url,true);
  21. //2.找到对应的路由,执行回调方法
  22. for(leti=0;i<app.routes.length;i++){
  23. let{method,path,handler}=app.routes[i]
  24. if(method===m&&path===pathname){
  25. handler(req,res);
  26. }
  27. }
  28. res.end('hahha')
  29. })
  30. server.listen(…arguments);
  31. }
  32. returnapp;
  33. }
  34. module.exports=createApplication;

运行一下代码。手写Express核心原理,再也不怕面试官问我Express原理

可见运行成功:

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

实现post等其他方法。

很简单,我们可以直接复制app.get方法,然后将method的值改成post就好了。

  1. //myExpress.js
  2. lethttp=require('http');
  3. consturl=require('url');
  4. functioncreateApplication(){
  5. 。。。
  6. app.get=function(path,handler){
  7. letlayer={
  8. method:'get',
  9. path,
  10. handler
  11. }
  12. app.routes.push(layer)
  13. }
  14. app.post=function(path,handler){
  15. letlayer={
  16. method:'post',
  17. path,
  18. handler
  19. }
  20. app.routes.push(layer)
  21. }
  22. 。。。
  23. returnapp;
  24. }
  25. module.exports=createApplication;

这样是可以实现,但是除了post和get,还有其他方法啊,难道每一个我们都要这样写嘛?,当然不是,有个很简单的方法。

// myExpress.js

  1. functioncreateApplication(){
  2. http.METHODS.forEach(method=>{
  3. method=method.toLocaleLowerCase()
  4. app[method]=function(path,handler){
  5. letlayer={
  6. method,
  7. path,
  8. handler
  9. }
  10. app.routes.push(layer)
  11. }
  12. });
  13. }
  14. module.exports=createApplication;

如代码所示,http.METHODS是一个方法数组。如下面所示的数组

  1. ["GET","POST","DELETE","PUT"]。

遍历方法数组,就可以实现所有方法了。

测试跑了一下,确实成功。

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

实现app.all方法

all表示的是匹配所有的方法,

app.all('/user')表示匹配所有路径是/user的路由

app.all('*')表示匹配任何路径 任何方法 的 路由

实现all方法也非常简单,如下代码所示

  1. app.all=function(path,handler){
  2. letlayer={
  3. method:"all",
  4. path,
  5. handler
  6. }
  7. app.routes.push(layer)
  8. }

然后只需要续改下路由器匹配的逻辑,如下代码所示,只需要修改下判断。

  1. app.listen=function(){
  2. letserver=http.createServer(function(req,res){
  3. //取出layer
  4. //1.获取请求的方法
  5. letm=req.method.toLocaleLowerCase();
  6. let{pathname}=url.parse(req.url,true);
  7. //2.找到对应的路由,执行回调方法
  8. for(leti=0;i<app.routes.length;i++){
  9. let{method,path,handler}=app.routes[i]
  10. if((method===m||method==='all')&&(path===pathname||path==="*")){
  11. handler(req,res);
  12. }
  13. }
  14. console.log(app.routes);
  15. res.end('hahha')
  16. })
  17. server.listen(…arguments);
  18. }

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

可见成功。

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

中间件app.use的实现

这个方法的实现,跟其他方法差不多,如代码所示。

  1. app.use=function(path,handler){
  2. letlayer={
  3. method:"middle",
  4. path,
  5. handler
  6. }
  7. app.routes.push(layer)
  8. }

但问题来了,使用中间件的时候,我们会使用next方法,来让程序继续往下执行,那它是怎么执行的。

  1. app.use(function(req,res,next){
  2. console.log('Time:',Date.now());
  3. next();
  4. });

所以我们必须实现next这个方法。

其实可以猜想,next应该就是一个疯狂调用自己的方法。也就是递归

而且每递归一次,就把被push到routes里的handler拿出来执行。

实际上,不管是app.use还说app.all还是app.get。其实都是把layer放进routes里,然后再统一遍历routes来判断该不该执行layer里的handler方法。可以看下next方法的实现。

  1. functionnext(){
  2. //已经迭代完整个数组,还是没有找到匹配的路径
  3. if(index===app.routes.length)returnres.end('Cannotfind')
  4. let{method,path,handler}=app.routes[index++]//每次调用next就去下一个layer
  5. if(method==='middle'){//处理中间件
  6. if(path==='/'||path===pathname||pathname.starWidth(path+'/')){
  7. handler(req,res,next)
  8. }else{//继续遍历
  9. next();
  10. }
  11. }else{//处理路由
  12. if((method===m||method==='all')&&(path===pathname||path==="*")){
  13. handler(req,res);
  14. }else{
  15. next();
  16. }
  17. }
  18. }

可以看到是递归方法的遍历routes数组。

而且我们可以发现,如果是使用中间件的话,那么只要path是“/”或者前缀匹配,这个中间件就会执行。由于handler会用到参数req和res。所以这个next方法要在 listen里面定义。

如下代码所示:

  1. //myExpress.js
  2. lethttp=require('http');
  3. consturl=require('url');
  4. functioncreateApplication(){
  5. letapp={};
  6. app.routes=[];
  7. letindex=0;
  8. app.use=function(path,handler){
  9. letlayer={
  10. method:"middle",
  11. path,
  12. handler
  13. }
  14. app.routes.push(layer)
  15. }
  16. app.all=function(path,handler){
  17. letlayer={
  18. method:"all",
  19. path,
  20. handler
  21. }
  22. app.routes.push(layer)
  23. }
  24. http.METHODS.forEach(method=>{
  25. method=method.toLocaleLowerCase()
  26. app[method]=function(path,handler){
  27. letlayer={
  28. method,
  29. path,
  30. handler
  31. }
  32. app.routes.push(layer)
  33. }
  34. });
  35. app.listen=function(){
  36. letserver=http.createServer(function(req,res){
  37. //取出layer
  38. //1.获取请求的方法
  39. letm=req.method.toLocaleLowerCase();
  40. let{pathname}=url.parse(req.url,true);
  41. //2.找到对应的路由,执行回调方法
  42. functionnext(){
  43. //已经迭代完整个数组,还是没有找到匹配的路径
  44. if(index===app.routes.length)returnres.end('Cannotfind')
  45. let{method,path,handler}=app.routes[index++]//每次调用next就去下一个layer
  46. if(method==='middle'){//处理中间件
  47. if(path==='/'||path===pathname||pathname.starWidth(path+'/')){
  48. handler(req,res,next)
  49. }else{//继续遍历
  50. next();
  51. }
  52. }else{//处理路由
  53. if((method===m||method==='all')&&(path===pathname||path==="*")){
  54. handler(req,res);
  55. }else{
  56. next();
  57. }
  58. }
  59. }
  60. next()
  61. res.end('hahha')
  62. })
  63. server.listen(…arguments);
  64. }
  65. returnapp;
  66. }
  67. module.exports=createApplication;

当我们请求路径就会发现中间件确实执行成功。

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

不过,这里的中间价实现还不够完美。

因为,我们使用中间件的时候,是可以不用传递路由的。例如:

  1. app.use((req,res)=>{
  2. console.log("我是没有路由的中间价");
  3. })

这也是可以使用的,那该怎么实现呢,其实非常简单,判断一下有没有传递路径就好了,没有的话,就给个默认路径“/”,实现代码如下:

  1. app.use=function(path,handler){
  2. if(typeofpath!=="string"){//第一个参数不是字符串,说明不是路径,而是方法
  3. handler=path;
  4. path="/"
  5. }
  6. letlayer={
  7. method:"middle",
  8. path,
  9. handler
  10. }
  11. app.routes.push(layer)
  12. }

看,是不是很巧妙,很容易。

我们试着访问路径“/middle”

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

咦?第一个中间件没有执行,为什么呢?

对了,使用中间件的时候,最后要执行next(),才能交给下一个中间件或者路由执行。

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

当我们请求“/middle”路径的时候,可以看到确实请求成功,中间件也成功执行。说明我们的逻辑没有问题。

实际上,中间件已经完成了,但是别忘了,还有个错误中间件?

什么是错误中间件?

错误处理中间件函数的定义方式与其他中间件函数基本相同,差别在于错误处理函数有四个自变量而不是三个,专门具有特征符 (err, req, res, next):

  1. app.use(function(err,req,res,next){
  2. console.error(err.stack);
  3. res.status(500).send('Somethingbroke!');
  4. });

当我们的在执行next()方法的时候,如果抛出了错误,是会直接寻找错误中间件执行的,而不会去执行其他的中间件或者路由。

举个例子:

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

如图所示,当第一个中间件往next传递参数的时候,表示执行出现了错误。然后就会跳过其他陆游和中间件和路由,直接执行错误中间件。当然,执行完错误中间件,就会继续执行后面的中间件。

例如:

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

如图所示,错误中间件的后面那个是会执行的。

原理该怎么实现呢?

很简单,直接看代码解释,只需在next里多加一层判断即可:

  1. functionnext(err){
  2. //已经迭代完整个数组,还是没有找到匹配的路径
  3. if(index===app.routes.length)returnres.end('Cannotfind')
  4. let{method,path,handler}=app.routes[index++]//每次调用next就去下一个layer
  5. if(err){//如果有错误,应该寻找中间件执行。
  6. if(handler.length===4){//找到错误中间件
  7. handler(err,req,res,next)
  8. }else{//继续徐州
  9. next(err)
  10. }
  11. }else{
  12. if(method==='middle'){//处理中间件
  13. if(path==='/'||path===pathname||pathname.starWidth(path+'/')){
  14. handler(req,res,next)
  15. }else{//继续遍历
  16. next();
  17. }
  18. }else{//处理路由
  19. if((method===m||method==='all')&&(path===pathname||path==="*")){
  20. handler(req,res);
  21. }else{
  22. next();
  23. }
  24. }
  25. }
  26. }

看代码可见在next里判断err有没有值,就可以判断需不需要查找错误中间件来执行了。

如图所示,请求/middle路径,成功执行。

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

到此,express框架的实现就大功告成了。

学习总结

通过这次express手写原理的实现,更加深入地了解了express的使用,发现:

中间件和路由都是push进一个routes数组里的。

当执行中间件的时候,会传递next,使得下一个中间件或者路由得以执行

当执行到路由的时候就不会传递next,也使得routes的遍历提前结束

当执行完错误中间件后,后面的中间件或者路由还是会执行的。

原文链接:https://mp.weixin.qq.com/s/iN-ZYJcBqVAHjop5qkyJkA

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 手写Express核心原理,再也不怕面试官问我Express原理 https://www.kuaiidc.com/92927.html

相关文章

发表评论
暂无评论