这是渐进式Express源码学习 | 小白也能懂源码系列文章的第五篇。
请结合该节代码阅读Lesson5-全副武装
目标特性和目标用法
这篇文章我们在第四篇文章的基础上,实现一个稍微加强版的Express,功能包括
- 可以获取req.params
- 提供app.param能力
这篇文章要实现的express的期望用法如下
const express = require('../index.js')
const app = express()
app.get('/user/:userId', function (req, res, next) {
res.end(`Welcome, the user.id = ${req.params.userId} and the user.name is ${req.user.name}`)
})
app.param('userId', function (req, res, next, userId, key) {
req.user = {
id: userId,
name: 'foo'
}
next()
})
app.get('/article/:title', function (req, res, next) {
res.end(`Welcome, the article's title is ${req.params.title}`)
})
app.listen(3000)
复制代码
如果你对app.param函数和req.params不熟,请在阅读这个文章之前,先看express文档,了解这两个东西的用法,否则不好理解这节课
源码及讲解
核心实现:1. layer借助path-to-regexp提取params。 2.在Router.handle里面,process_params函数依次调用参数处理函数
这节课里,和上一节课,主要的变化体现在2个方面,其他的文件的变化很好理解,这里不做解释
- lib/route/layer.js
- 在match函数里面,获取到req对象的params值
- lib/route/index.js
- 增加process_params
- 增加param函数
- handle
- 把layer的params复制req
- 调用process_params
首先我们看layer.match函数
还记得这个layer.match是在哪里调用的吗?是在请求到来的时候,Router.handle里面调用的,也就是说我们这个时候有了req,也有了req的path。
看第52行的keys,还记得这个keys是什么吗?没错,就是保存了参数对象的数组[{name: ‘userId’}]这样的。 52-61行做的事情,就是在path匹配的情况下,把path里面的参数值提取出来,放到layer.params里面
再看lib/route/index.js文件,先看Router.param函数,这个函数是我们在调用app.param函数的时候,底层调用的函数
从图中我们可以看到,他其实什么也没做,只是把fn保存在了数组里 我们再看Router.process_params函数
这个函数里面,有两层递归,分别是param()和paramCallback()。对这两个函数,简单的说,有几个参数就会调用几次param,计数器是keys.length(代码86行),例如
app.get('/user/:userId')
// 这个只有一个参数userId,param()只会被调用一次
app.get('/order/:type/:state')
// 这个有两个参数,分别是type和state,param()会被调用两次
复制代码
而paramCallback是当前参数有几个处理函数,就调用几次,计数器是paramIndex(代码101行),每个参数都会清空,例如
app.param('userId', fn1)
// 这个userId只有一个处理函数,paramCallback只会调用一次
app.param('userId', fn1, fn2)
// 这个userId有两个处理函数,paramCallback会调用两次
复制代码
我们可以通过例子具体讲解param()和paramCallback()的递归
app.get('/user/:userId', fn)
app.param('userId', handle)
复制代码
上面这个例子里,按时间顺序
- param调用,对应参数userId
- paramCallback调用,对应参数handle
app.get('/order/:type/:state', fn)
app.param('type', handle1, handle2)
app.param('state', handle3)
复制代码
上面这个例子里,按时间顺序
- param调用,对应参数type
- paramCallback调用,对应参数handle1
- paramCallback调用,对应参数handle2
- param调用,对应参数state
- paramCallback调用,对应参数handle3
具体实现可以看代码
本文总结
本文实现了一个加强的Express,拥有app.param能力,也可以获取req.params