在现代 Web 开发中,无论是 Express、Koa 还是 Fastify,路径参数(params) 都是构建 RESTful API 的基础。而要真正理解 req.params 背后的原理,甚至实现自己的路由系统,正则表达式(Regex) 是绕不开的核心技能,前面浅浅来了解了查询参数(query),今天来了解一下params吧。
本文将从 URL 路径参数的识别机制 出发,深入讲解正则表达式的关键符号、书写规范,并结合 Node.js 实战演示如何用正则精准提取 params,最后附上大厂高频面试题,助你一举攻克这一难点。
一、什么是 params?它从何而来?
在 Express 中,我们常这样定义路由:
js
编辑
app.get('/user/:id', (req, res) => {
console.log(req.params.id); // 如访问 /user/123,则输出 "123"
});
这里的 :id 就是一个 路径参数(path parameter) 。但框架底层是如何从 /user/123 中“识别”出 id = "123" 的?
答案是:通过正则表达式匹配并捕获(capture) 。
✅ 简单说:params 的本质,就是正则表达式中的捕获组(capturing groups) 。
二、正则表达式核心符号详解(聚焦 params 场景)
要写好用于解析 params 的正则,必须掌握以下符号:
| 符号 | 含义 | 示例 | 在 params 中的作用 |
|---|---|---|---|
^ | 匹配字符串开头 | ^/user | 确保路径以 /user 开头,防止 /admin/user/123 误匹配 |
$ | 匹配字符串结尾 | 123$ | 确保路径结束,防止 /user/123/extra 被匹配 |
\w | 匹配字母、数字、下划线 [a-zA-Z0-9_] | \w+ | 匹配合法的参数名或值(如 :username 对应 alice_2024) |
\d | 匹配数字 [0-9] | \d{3,5} | 匹配纯数字 ID(如用户ID、订单号) |
() | 捕获组 —— 提取关键内容 | (\w+) | 核心! 括号内的内容会被提取为 params 值 |
+ | 前面元素出现 1 次或多次 | \w+ | 确保参数非空(不能是 /user/) |
* | 前面元素出现 0 次或多次 | \w* | 允许空值(一般不用于 params) |
? | 前面元素出现 0 或 1 次 | (\d?) | 可选参数(较少见) |
/ | 转义斜杠 / | / | 匹配 URL 中的实际 / 字符 |
⚠️ 注意:在 JavaScript 字符串中写正则时,反斜杠需双重转义,如
"\/\w+";但用字面量/.../则只需单层://\w+/。
三、经典 params 正则模式拆解
1. 基础路径参数:/province/:city
目标:匹配 /江西/南昌,提取 province="江西", city="南昌"
正则表达式:
/^/(\w+)/(\w+)$/
逐段解析:
^:从开头匹配/:匹配第一个/(\w+):第一个捕获组 →province/:匹配中间的/(\w+):第二个捕获组 →city$:到结尾结束
Node.js 提取示例:
const path = '/江西/南昌';
const reg = /^/(\w+)/(\w+)$/;
const match = path.match(reg);
if (match) {
const params = {
province: match[1], // "江西"
city: match[2] // "南昌"
};
console.log(params);
}
2. 数字型 ID:/user/:id
目标:只允许数字 ID,如 /user/123,拒绝 /user/abc
正则表达式:
/^/user/(\d+)$/
(\d+)确保 ID 为纯数字- 若需限制长度(如 6~10 位):
/^/user/(\d{6,10})$/
3. 复杂路径:/api/v1/order/:orderId/item/:itemId
正则表达式:
/^/api/v1/order/(\w+)/item/(\w+)$/
- 两个捕获组分别对应
orderId和itemId - 非参数部分(如
/api/v1/order/)需原样写出并转义/
四、正则书写规范与最佳实践
✅ 规范建议:
-
始终使用
^和$
避免部分匹配导致的安全问题(如/user/123/admin被误认为/user/123)。 -
明确参数类型
- 用户名:
[\w\u4e00-\u9fa5]+(支持中文) - 手机号:
1\d{10} - UUID:
[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}
- 用户名:
-
避免过度宽泛
❌ 错误:/.*(匹配任意路径,无法提取 params)
✅ 正确:/^/post/(\d+)$/ -
命名清晰(在代码中)
虽然正则本身无命名,但可通过注释或变量名说明:// 匹配 /article/:slug const ARTICLE_PATH = /^/article/([\w-]+)$/;
五、Node.js 实战:手写简易路由 params 解析器
function createRouteMatcher(pathTemplate) {
// 将 :param 转为 (\w+),并转义其他 /
const regexStr = pathTemplate
.replace(///g, '\/') // 转义所有 /
.replace(/:(\w+)/g, '(\w+)'); // :id → (\w+)
const regex = new RegExp(`^${regexStr}$`);
const paramNames = [...pathTemplate.matchAll(/:(\w+)/g)].map(m => m[1]);
return (path) => {
const match = path.match(regex);
if (!match) return null;
const params = {};
for (let i = 0; i < paramNames.length; i++) {
params[paramNames[i]] = match[i + 1];
}
return params;
};
}
// 使用
const matchUser = createRouteMatcher('/user/:id');
console.log(matchUser('/user/456')); // { id: '456' }
const matchLocation = createRouteMatcher('/province/:province/city/:city');
console.log(matchLocation('/province/广东/city/深圳')); // { province: '广东', city: '深圳' }
💡 这正是 Express/Koa 路由引擎的简化版原理!
六、大厂高频面试题(附解析)
📌 面试题 1:如何用正则匹配带中文的省份路径,如 /省份/江西省/城市/南昌市?
答:
const reg = /^/省份/([\u4e00-\u9fa5]+)/城市/([\u4e00-\u9fa5]+)$/;
// \u4e00-\u9fa5 是中文 Unicode 范围
📌 面试题 2:以下正则有什么问题?如何修复?
const reg = //user/(.*)/;
用于匹配 /user/123
答:
-
问题1:缺少
^和$,会匹配/admin/user/123/profile -
问题2:
.*会贪婪匹配到最后,且包含非法字符 -
修复:
const reg = /^/user/(\d+)$/;
📌 面试题 3:如何实现一个支持可选参数的路由,如 /search/:keyword?(keyword 可省略)?
答:
// 方案:用 ? 表示可选
const reg = /^/search(?:/(\w+))?$/;
console.log('/search'.match(reg)); // ["/search", undefined]
console.log('/search/node'.match(reg)); // ["/search/node", "node"]
(?:...)是非捕获组,?表示整个/keyword部分可有可无。
📌 面试题 4:为什么 Express 不直接用正则,而要用 :param 语法?
答:
- 可读性:
:userId比(\d+)更语义化 - 安全性:框架可对
:param默认做类型/长度校验 - 扩展性:支持自定义参数匹配器(如 Express 的
app.param())
七、总结
- params 的本质是正则的捕获组
() - 写好 params 正则:锚定头尾(
^$)+ 明确类型(\w/\d)+ 合理捕获 - 掌握
\w、\d、()、^、$、/等符号是基础 - 大厂考察重点:边界处理、安全匹配、中文支持、可选参数
🔑 记住:正则不是魔法,而是精确的字符串规则描述。当你能用正则清晰表达“我想要什么样的路径”,你就真正掌握了 params 的灵魂。
现在,试着自己写一个匹配 /blog/:year(\d{4})/:month(\d{2})/:slug 的正则吧,体验一下正则表达式强大的力量吧!