
本文介绍一种简洁、安全且可扩展的方式,将形如 "0...100 & [70...80, 0...100] & 30...50" 的结构化范围字符串,递归解析为合法 JavaScript 表达式并求值,从而高效判断数值是否满足嵌套逻辑条件。
本文介绍一种简洁、安全且可扩展的方式,将形如 `"0...100 & [70...80, 0...100] & 30...50"` 的结构化范围字符串,递归解析为合法 javascript 表达式并求值,从而高效判断数值是否满足嵌套逻辑条件。
在前端开发中,常需对用户输入的动态范围表达式进行校验(例如权限配置、表单验证或规则引擎)。原始 inRange 函数已支持基础逻辑(& 表示 AND、, 表示 OR、... 表示区间),但面对嵌套结构(如 [70...80, 0...100])时,手动递归解析括号层级易出错、可维护性差。更优解是语法转换 + 安全求值:将自定义语法映射为标准 JavaScript 布尔表达式,再通过 Function 构造器执行(注意:仅限可信上下文)。
✅ 核心思路:正则驱动的语法转换
不直接手写递归解析器,而是利用正则表达式分步替换,将领域特定语法(DSL)转为等价 JS 代码:
- [ → (,] → ):提升嵌套优先级,匹配 JS 运算符结合律;
- & → &&,, → ||:统一逻辑运算符;
- X...Y → X <= num && num <= Y,X... → num >= X,...Y → num <= Y:将区间语法绑定到参数 num。
⚠️ 关键细节:使用 (?<=(?:\D|^)) 和 (?=(?:\D|$)) 等零宽断言,确保 ... 不被数字包围(如避免误替换 123...456 中的 ...),提升鲁棒性。
? 完整实现代码
const inRange = (num, str) => {
// 定义语法转换规则:[正则/字符串, 替换模板]
const substitutions = [
['&', ' && '], // AND 运算符
[/^(?<=\D|^)\.\.\.(\d+)/g, 'num <= $1'], // "...100" → "num <= 100"
[/(\d+)\.\.\.(?=\D|$)/g, 'num >= $1'], // "0..." → "num >= 0"
[/(\d+)\.\.\.(\d+)/g, '$1 <= num && num <= $2'], // "0...100" → "0 <= num && num <= 100"
[',', ' || '], // OR 运算符
['[', ' ( '], // 开括号 → 左括号(带空格防粘连)
[']', ' ) '] // 闭括号 → 右括号
];
// 批量替换
let expr = str;
for (const [from, to] of substitutions) {
expr = typeof from === 'string'
? expr.replaceAll(from, to)
: expr.replaceAll(from, to);
}
// 调试用:输出生成的 JS 表达式
console.log('Evaluated expression:', expr);
try {
// 安全构造函数:作用域隔离,仅接收 num 参数
const fn = new Function('num', 'return ' + expr);
return fn(num);
} catch (e) {
throw new Error(`Invalid range expression: "${str}" → ${e.message}`);
}
};
// ✅ 使用示例
console.log(inRange(100, "0...")); // true (100 >= 0)
console.log(inRange(100, "...100")); // true (100 <= 100)
console.log(inRange(75, "0...100 & 70...80, 0...100 & [30...50, 60...90]")); // true
console.log(inRange(75, "0...100 & [70...80, 0...100] & 30...50")); // false⚠️ 注意事项与最佳实践
- 安全性前提:new Function() 仅适用于完全可信的输入源(如配置文件、内部管理后台)。若 rngStr 来自用户输入,必须先做白名单校验(如仅允许数字、...、&、,、[、]、空格),或改用 AST 解析器(如 acorn)。
- 空格处理:替换中加入空格(如 ' ( ')可避免 &&( 等非法 JS 语法;生产环境建议额外调用 .replace(/\s+/g, ' ').trim() 规范空白。
- 错误反馈:封装 try/catch 提供清晰错误信息,便于调试非法表达式。
- 性能考量:对于高频调用场景,可缓存 Function 实例(基于 rngStr 哈希),避免重复编译。
? 总结
该方案以“语法即代码”思想,绕过复杂递归解析,用声明式正则完成 DSL 到 JS 的精准映射。它兼具简洁性(<20 行核心逻辑)、可读性(规则一目了然)和扩展性(新增语法只需追加替换项)。当业务需要快速支持嵌套逻辑且输入可控时,这是比手写解析器更务实、更可靠的选择。
立即学习“Java免费学习笔记(深入)”;


















