# 【建议收藏】25+正则面试题详尽解析，让你轻松通过正则面试，让你少写2000行代码

English

## 1. 数字价格千分位分割

`这道题估计大家在面试和工作中也经常遇到，出现频率比较高。`

### 正则结果

``'123456789'.replace(/(?!^)(?=(\d{3})+\$)/g, ',') // 123,456,789``

### 分析过程

1. 从后往前`每三个数字`前加一个逗号
2. 开头不能加逗号(比如：`123` 最后不能变成`,123`)

``````
let price = '123456789'
let priceReg = /(?=\d{3}\$)/

console.log(price.replace(proceReg, ',')) // 123456,789``````

``````
let price = '123456789'
let priceReg = /(?=(\d{3})+\$)/g

console.log(price.replace(priceReg, ',')) // ,123,456,789
``````

``````
let price = '123456789'
let priceReg = /(?!^)(?=(\d{3})+\$)/g

console.log(price.replace(priceReg, ',')) // 123,456,789``````

## 2. 手机号3-4-4分割

`表单搜集场景，经常遇到的手机格式化`

### 正则结果

``````let mobile = '18379836654'
let mobileReg = /(?=(\d{4})+\$)/g

console.log(mobile.replace(mobileReg, '-')) // 183-7983-6654
``````

### 分析过程

``````
let mobile = '18379836654'
let mobileReg = /(?=(\d{4})+\$)/g

console.log(mobile.replace(mobileReg, '-')) // 183-7983-6654
``````

## 3. 手机号3-4-4分割扩展

1. 123 => 123
2. 1234 => 123-4
3. 12345 => 123-45
4. 123456 => 123-456
5. 1234567 => 123-4567
6. 12345678 => 123-4567-8
7. 123456789 => 123-4567-89
8. 12345678911 => 123-4567-8911

### 正则结果

``````
const formatMobile = (mobile) => {
return String(mobile).slice(0,11)
.replace(/(?<=\d{3})\d+/, (\$0) => '-' + \$0)
.replace(/(?<=[\d-]{8})\d{1,4}/, (\$0) => '-' + \$0)
}

console.log(formatMobile(18379836654))
``````

### 分析过程

``````const formatMobile = (mobile) => {
return String(mobile).replace(/(?<=\d{3})\d+/, '-')
}

console.log(formatMobile(123)) // 123
console.log(formatMobile(1234)) // 123-4
``````

``````const formatMobile = (mobile) => {
return String(mobile).slice(0,11)
.replace(/(?<=\d{3})\d+/, (\$0) => '-' + \$0)
.replace(/(?<=[\d-]{8})\d{1,4}/, (\$0) => '-' + \$0)
}

console.log(formatMobile(123)) // 123
console.log(formatMobile(1234)) // 123-4
console.log(formatMobile(12345)) // 123-45
console.log(formatMobile(123456)) // 123-456
console.log(formatMobile(1234567)) // 123-4567
console.log(formatMobile(12345678)) // 123-4567-8
console.log(formatMobile(123456789)) // 123-4567-89
console.log(formatMobile(12345678911)) // 123-4567-8911
``````

## 4。 验证密码的合法性

### 正则结果

``````let reg = /(((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))|(?=.*[a-z])(?=.*[A-Z]))^[a-zA-Z\d]{6,12}\$/

console.log(reg.test('123456')) // false
console.log(reg.test('aaaaaa')) // false
console.log(reg.test('AAAAAAA')) // false
console.log(reg.test('1a1a1a')) // true
console.log(reg.test('1A1A1A')) // true
console.log(reg.test('aAaAaA')) // true
console.log(reg.test('1aA1aA1aA')) // true
``````

### 分析过程

1. 密码长度是6-12位
2. 由数字、小写字符和大写字母组成
3. 必须至少包括2种字符

``````let reg = /^[a-zA-Z\d]{6,12}\$/
``````

``````let reg = /(?=.*\d)/
// 这个正则的意思是，匹配的是一个位置
// 这个位置需要满足`任意数量的符号，紧跟着是个数字`，
// 注意它最终得到的是个位置而不是其他的东西
// (?=.*\d)经常用来做条件限制

console.log(reg.test('hello')) // false
console.log(reg.test('hello1')) // true
console.log(reg.test('hel2lo')) // true

// 其他类型同理
``````

1. 数字和小写字母组合
2. 数字和大写字母组合
3. 小写字母与大写字母组合
4. 数字、小写字母、大写字母一起组合（但其实前面三种已经覆盖了第四种了）
``````// 表示条件1和2
// let reg = /((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))/
// 表示条件条件3
// let reg = /(?=.*[a-z])(?=.*[A-Z])/
// 表示条件123
// let reg = /((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))|(?=.*[a-z])(?=.*[A-Z])/
// 表示题目所有条件
let reg = /(((?=.*\d)((?=.*[a-z])|(?=.*[A-Z])))|(?=.*[a-z])(?=.*[A-Z]))^[a-zA-Z\d]{6,12}\$/

console.log(reg.test('123456')) // false
console.log(reg.test('aaaaaa')) // false
console.log(reg.test('AAAAAAA')) // false
console.log(reg.test('1a1a1a')) // true
console.log(reg.test('1A1A1A')) // true
console.log(reg.test('aAaAaA')) // true
console.log(reg.test('1aA1aA1aA')) // true
``````

## 5. 提取连续重复的字符

### 正则结果

``````const collectRepeatStr = (str) => {
let repeatStrs = []
const repeatRe = /(.+)\1+/g

str.replace(repeatRe, (\$0, \$1) => {
\$1 && repeatStrs.push(\$1)
})

return repeatStrs
}
``````

### 分析过程

1. 连续重复的字符
2. 连续重复的字符数的长度是不限的（如23、45是两位、6是一位）

`那什么是连续重复呢？`

11是连续重复、22也是连续重复、111当然也是。也就是说某些字符X之后一定也是跟着X，就叫连续重复。如果很明确知道X是就是1，那么`/11+/`也就可以匹配了，但关键是这里的X是不明确的，怎么办呢？。

``````// 这里的X可用.来表示，即所有的字符，并用括号进行引用，紧跟着反向应用\1，也就是体现了连续重复的意思啦
let repeatRe = /(.)\1/

console.log(repeatRe.test('11')) // true
console.log(repeatRe.test('22')) // true
console.log(repeatRe.test('333')) // true
console.log(repeatRe.test('123')) // false
``````

``````
let repeatRe = /(.+)\1+/

console.log(repeatRe.test('11')) // true
console.log(repeatRe.test('22')) // true
console.log(repeatRe.test('333')) // true
console.log(repeatRe.test('454545')) // true
console.log(repeatRe.test('124')) // false
``````

``````
const collectRepeatStr = (str) => {
let repeatStrs = []
const repeatRe = /(.+)\1+/g
// 很多时候replace并不是用来做替换，而是做数据提取用
str.replace(repeatRe, (\$0, \$1) => {
\$1 && repeatStrs.push(\$1)
})

return repeatStrs
}

console.log(collectRepeatStr('11')) // ["1"]
console.log(collectRepeatStr('12323')) // ["23"]
console.log(collectRepeatStr('12323454545666')) // ["23", "45", "6"]
``````

## 6. 实现一个trim函数

### 正则结果

``````// 去除空格法
const trim = (str) => {
return str.replace(/^\s*|\s*\$/g, '')
}
// 提取非空格法
const trim = (str) => {
return str.replace(/^\s*(.*?)\s*\$/g, '\$1')
}
``````

### 分析过程

``````
const trim = (str) => {
return str.replace(/^\s*|\s*\$/g, '')
}

console.log(trim('  前端胖头鱼')) // 前端胖头鱼
console.log(trim('前端胖头鱼  ')) // 前端胖头鱼
console.log(trim('  前端胖头鱼  ')) // 前端胖头鱼
console.log(trim('  前端 胖头鱼  ')) // 前端 胖头鱼
``````

``````
const trim = (str) => {
return str.replace(/^\s*(.*?)\s*\$/g, '\$1')
}

console.log(trim('  前端胖头鱼')) // 前端胖头鱼
console.log(trim('前端胖头鱼  ')) // 前端胖头鱼
console.log(trim('  前端胖头鱼  ')) // 前端胖头鱼
console.log(trim('  前端 胖头鱼  ')) // 前端 胖头鱼
``````

## 7. HTML转义

`&``&amp;`
`<``&lt;`
`>``&gt;`
`"``&quot;`
`'``&#x27;`

### 正则结果

``````
const escape = (string) => {
const escapeMaps = {
'&': 'amp',
'<': 'lt',
'>': 'gt',
'"': 'quot',
"'": '#39'
}
const escapeRegexp = new RegExp(`[\${Object.keys(escapeMaps).join('')}]`, 'g')

return string.replace(escapeRegexp, (match) => `&\${escapeMaps[match]};`)
}

``````

### 分析过程

``````const escape = (string) => {
const escapeMaps = {
'&': 'amp',
'<': 'lt',
'>': 'gt',
'"': 'quot',
"'": '#39'
}
// 这里和/[&<>"']/g的效果是一样的
const escapeRegexp = new RegExp(`[\${Object.keys(escapeMaps).join('')}]`, 'g')

return string.replace(escapeRegexp, (match) => `&\${escapeMaps[match]};`)
}

console.log(escape(`
<div>
<p>hello world</p>
</div>
`))

/*
&lt;div&gt;
&lt;p&gt;hello world&lt;/p&gt;
&lt;/div&gt;

*/
``````

## 8. HTML反转义

### 正则结果

``````const unescape = (string) => {
const unescapeMaps = {
'amp': '&',
'lt': '<',
'gt': '>',
'quot': '"',
'#39': "'"
}

const unescapeRegexp = /&([^;]+);/g

return string.replace(unescapeRegexp, (match, unescapeKey) => {
return unescapeMaps[ unescapeKey ] || match
})
}

console.log(unescape(`
&lt;div&gt;
&lt;p&gt;hello world&lt;/p&gt;
&lt;/div&gt;
`))

/*
<div>
<p>hello world</p>
</div>
*/
``````

## 9. 将字符串驼峰化

``````1. foo Bar => fooBar

2. foo-bar---- => fooBar

3. foo_bar__ => fooBar
``````

### 正则结果

``````const camelCase = (string) => {
const camelCaseRegex = /[-_\s]+(.)?/g

return string.replace(camelCaseRegex, (match, char) => {
return char ? char.toUpperCase() : ''
})
}
``````

### 分析过程

1. 每个单词的前面都有0个或者多个`-` `空格` `_` 如(`Foo``--foo``__FOO``_BAR`` Bar`)
2. `-` `空格` `_`后面有可能不跟任何东西 如(`__``--`)
``````const camelCase = (string) => {
// 注意(.)?这里的?是为了满足条件2
const camelCaseRegex = /[-_\s]+(.)?/g

return string.replace(camelCaseRegex, (match, char) => {
return char ? char.toUpperCase() : ''
})
}

console.log(camelCase('foo Bar')) // fooBar
console.log(camelCase('foo-bar--')) // fooBar
console.log(camelCase('foo_bar__')) // fooBar
``````

## 10. 将字符串首字母转化为大写，剩下为小写

### 正则结果

``````
const capitalize = (string) => {
const capitalizeRegex = /(?:^|\s+)\w/g

return string.toLowerCase().replace(capitalizeRegex, (match) => match.toUpperCase())
}
``````

### 分析过程

``````
const capitalize = (string) => {
const capitalizeRegex = /(?:^|\s+)\w/g

return string.toLowerCase().replace(capitalizeRegex, (match) => match.toUpperCase())
}

console.log(capitalize('hello world')) // Hello World
console.log(capitalize('hello WORLD')) // Hello World
``````

## 11. 获取网页中所有img标签的图片地址

### 分析过程

1. 图片标签`img`
2. 需要是在线链接形式，一些base64的图片需要过滤掉

``````
const matchImgs = (sHtml) => {
const imgUrlRegex = /<img[^>]+src="((?:https?:)?\/\/[^"]+)"[^>]*?>/gi
let matchImgUrls = []

sHtml.replace(imgUrlRegex, (match, \$1) => {
\$1 && matchImgUrls.push(\$1)
})

return matchImgUrls
}

``````

1. img标签到src之间的部分，只要不是>，其他的啥都可以
2. 括号内的部分，也就是我们要提取的url部分，作为一个捕获分组存在，方便直接获取

2.1 (?:https?:)? 表示支持协议头为http:或者https:

2.2 括号外面的？，表示可以没有协议头，即支持`//xxx.juejjin.com/a.jpg`形式的链接

2.3 接着是两个斜线

2.4 因为src="" 双引号内的部分即为链接，所以`[^"]+` 表示除了"其他部分都行

3. 接着就是"到img结束标签>之间的部分了，除了>之外，啥都可以 `[^>]*?`

## 12.通过name获取url query参数

### 正则结果

``````const getQueryByName = (name) => {
const queryNameRegex = new RegExp(`[?&]\${name}=([^&]*)(&|\$)`)
const queryNameMatch = window.location.search.match(queryNameRegex)
// 一般都会通过decodeURIComponent解码处理
return queryNameMatch ? decodeURIComponent(queryNameMatch[1]) : ''
}
``````

### 分析过程

url query上的参数 `name=前端胖头鱼` 所处的位置可能是

1. `紧跟着问号` ?name=前端胖头鱼&sex=boy
2. `在最后的位置` ?sex=boy&name=前端胖头鱼
3. `在1和2之间` ?sex=boy&name=前端胖头鱼&age=100

1. name前面只能是?或者&
2. value的值可以除了是&以为的任意东西
3. value后面只能是跟着&或者是结束位置
``````
const getQueryByName = (name) => {
const queryNameRegex = new RegExp(`[?&]\${name}=([^&]*)(?:&|\$)`)
const queryNameMatch = window.location.search.match(queryNameRegex)
// 一般都会通过decodeURIComponent解码处理
return queryNameMatch ? decodeURIComponent(queryNameMatch[1]) : ''
}
// 1. name在最前面
// https://juejin.cn/?name=前端胖头鱼&sex=boy
console.log(getQueryByName('name')) // 前端胖头鱼

// 2. name在最后
// https://juejin.cn/?sex=boy&name=前端胖头鱼
console.log(getQueryByName('name')) // 前端胖头鱼

// 2. name在中间
// https://juejin.cn/?sex=boy&name=前端胖头鱼&age=100
console.log(getQueryByName('name')) // 前端胖头鱼

``````

## 13. 匹配24小时制时间

1. `01:14`
2. `1:14`
3. `1:1`
4. `23:59`

### 正则结果

``````const check24TimeRegexp = /^(?:(?:0?|1)\d|2[0-3]):(?:0?|[1-5])\d\$/
``````

### 分析过程

24小时制的时间的`时``分`分别需要满足

1. 第一位可以是012
2. 第二位

2.1 当第一位是01时，第二位可以是任意数字

2.2 当第二位是2时，第二位只能是0、1、2、3

1. 第一位可以是0、1、2、3、4、5
2. 第二位可以是任意数字

``````const check24TimeRegexp = /^(?:[01]\d|2[0-3]):[0-5]\d\$/

console.log(check24TimeRegexp.test('01:14')) // true
console.log(check24TimeRegexp.test('23:59')) // true
console.log(check24TimeRegexp.test('23:60')) // false

console.log(check24TimeRegexp.test('1:14')) // false 实际需要支持
console.log(check24TimeRegexp.test('1:1')) // false 实际需要支持

``````

``````const check24TimeRegexp = /^(?:(?:0?|1)\d|2[0-3]):(?:0?|[1-5])\d\$/

console.log(check24TimeRegexp.test('01:14')) // true
console.log(check24TimeRegexp.test('23:59')) // true
console.log(check24TimeRegexp.test('23:60')) // false

console.log(check24TimeRegexp.test('1:14')) // true
console.log(check24TimeRegexp.test('1:1')) // true
``````

## 14. 匹配日期格式

### 正则结果

``const checkDateRegexp = /^\d{4}([-\.\/])(?:0[1-9]|1[0-2])\1(?:0[1-9]|[12]\d|3[01])\$/``

### 分析过程

1. `yyyy年部分` 这部分只要是四个数字就可以`\d{4}`
2. `mm月份部分`

2.1 一年只有12个月，前10个月可以用`0\d`

2.2 10月份及其以后以后 `1[0-2]`

3. `dd日部分`

3.1 一个月最多是31日

3.2 最小是1号

``````const checkDateRegexp = /^\d{4}([-\.\/])(?:0[1-9]|1[0-2])\1(?:0[1-9]|[12]\d|3[01])\$/

console.log(checkDateRegexp.test('2021-08-22')) // true
console.log(checkDateRegexp.test('2021/08/22')) // true
console.log(checkDateRegexp.test('2021.08.22')) // true
console.log(checkDateRegexp.test('2021.08/22')) // false
console.log(checkDateRegexp.test('2021/08-22')) // false
``````

## 15. 匹配16进制的颜色值

### 正则结果

``````const matchColorRegex = /#(?:[\da-zA-Z]{6}|[\da-zA-Z]{3})/g
``````

### 分析过程

16进制的颜色值由以下两部分组成

1. `#`
2. 6位或3位 `数字``大小写字母`组成
``````
const matchColorRegex = /#(?:[\da-zA-Z]{6}|[\da-zA-Z]{3})/g
const colorString = '#12f3a1 #ffBabd #FFF #123 #586'

console.log(colorString.match(matchColorRegex))
// [ '#12f3a1', '#ffBabd', '#FFF', '#123', '#586' ]
``````

## 16. 检测URL前缀

### 正则结果

``````
const checkProtocol = /^https?:/

console.log(checkProtocol.test('https://juejin.cn/')) // true
console.log(checkProtocol.test('http://juejin.cn/')) // true
console.log(checkProtocol.test('//juejin.cn/')) // false
``````

## 17. 检测中文

### 分析过程

``````

const checkChineseRegex = /^[\u4E00-\u9FA5]+\$/

console.log(checkChineseRegex.test('前端胖头鱼'))
console.log(checkChineseRegex.test('1前端胖头鱼'))
console.log(checkChineseRegex.test('前端胖头鱼2'))

``````

## 18. 匹配手机号

### 解析过程

``````
const mobileRegex = /^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[235-8]\d{2}|4(?:0\d|1[0-2]|9\d))|9[0-35-9]\d{2}|66\d{2})\d{6}\$/

console.log(mobileRegex.test('18379867725'))
console.log(mobileRegex.test('123456789101'))
console.log(mobileRegex.test('+8618379867725'))
console.log(mobileRegex.test('8618379867725'))
``````

1. `(?:\+?86)?`: 手机前缀，括号内通过`?:`标识非引用分组
2. 1: 所有的手机号都是以1开头
3. (a|b|c|...): 2~5位的各种情况，通过多选分支|进行逐一解释
4. \d{6}: 6位任意数字

## 19. 英文单词加前后空格

### 解析过程

1. \w和\W之间的位置
2. ^与\w之间的位置
3. \w与\$之间的位置

``````
const wordRegex = /\b/g

console.log('you说来是come，去是go'.replace(/\b/g, ' ')) // ` you 说来是 come ，去是 go `
``````

## 20. 字符串大小写取反

### 解析过程

``````对于字符串 x = `A`

'A'.toUpperCase()得到的y是A

y === x

``````
const stringCaseReverseReg = /[a-z]/ig
const string = 'hello WORLD'

const string2 = string.replace(stringCaseReverseReg, (char) => {
const upperStr = char.toUpperCase()
// 大写转小写，小写转大写
return upperStr === char ? char.toLowerCase() : upperStr
})

console.log(string2) // HELLO world

``````

## 21. windows下的`文件`夹和文件路径

4. C:\

### 正则结果

``````
const windowsPathRegex = /^[a-zA-Z]:\\(?:[^\\:*<>|"?\r\n/]+\\?)*(?:(?:[^\\:*<>|"?\r\n/]+)\.\w+)?\$/;``````

### 解析过程

windows下的文件规则大概由这几部分构成

`磁盘符:\文件夹\文件夹\文件`

1. 磁盘符：只能是英文构成 `[a-zA_Z]:\\`
2. 文件夹名字：不包含一些特殊符号且可出现任意次,最后的\可以没有 `([^\\:*<>|"?\r\n/]+\\?)*`
3. 文件名字：`([^\\:*<>|"?\r\n/]+)\.\w+`，但是文件可以没有
``````const windowsPathRegex = /^[a-zA-Z]:\\(?:[^\\:*<>|"?\r\n/]+\\?)*(?:(?:[^\\:*<>|"?\r\n/]+)\.\w+)?\$/;

console.log( windowsPathRegex.test("C:\\") ); // true
``````

## 22. 匹配id（写爬虫获取html经常用到）

### 正则结果

``````
const matchIdRegexp = /id="([^"]*)"/

console.log(`
<div id="box">
hello world
</div>
`.match(matchIdRegexp)[1])
``````

### 解析过程

``````
<div id="box">
hello world
</div>
``````

``````const matchIdRegexp = /id="(.*)"/

console.log(`
<div id="box">
hello world
</div>
`.match(matchIdRegexp)[1])
``````

``````
const matchIdRegexp = /id="([^"]*)"/

console.log(`
<div id="box">
hello world
</div>
`.match(matchIdRegexp)[1])
``````

## 23. 匹配id 扩展（获取掘金首页html所有id）

### 正则结果

``````const idRegexp = /id="([^"]+)"/g

document.body.innerHTML
.match(idRegexp)
.map((idStr) => idStr.replace(idRegexp, '\$1'))
``````

## 24. 大于等于0, 小于等于150, 支持小数位出现5, 如145.5, 用于判断考卷分数

### 正则结果

``````
const pointRegex = /^150\$|^(?:[1-9]?\d|1[0-4]\d)(?:\.5)?\$/

``````

### 分析过程

1. 整数部分

1. 个位整数
2. 十位整数
3. 百位整数但小于150
2. 小数部分：只能是`.5` 或者没有

``````
// 1. 如何表示个位数？ /\d/
// 2. 如何表示十位数? /[1-9]\d/
// 3. 个位和十位如何一起表示？ /[1-9]?\d/
// 4. 小于150的百位数呢? /1[0-4]\d/

// 所以结合起来整数部分可以用以下正则表示

const pointRegex = /^150\$|^(?:[1-9]?\d|1[0-4]\d)?\$/

console.log(pointRegex.test(0)) // true
console.log(pointRegex.test(10)) // true
console.log(pointRegex.test(100)) // true
console.log(pointRegex.test(110.5)) // false
console.log(pointRegex.test(150)) // true
``````

``````
// 小数部分相对简单 /(?:\.5)?/，所以整体结合起来就是

const pointRegex = /^150\$|^(?:[1-9]?\d|1[0-4]\d)(?:\.5)?\$/

console.log(pointRegex.test(-1)) // false
console.log(pointRegex.test(0)) // true
console.log(pointRegex.test(10)) // true
console.log(pointRegex.test(100)) // true
console.log(pointRegex.test(110.5)) // true
console.log(pointRegex.test(150)) // true
console.log(pointRegex.test(151)) // false

``````

## 25. 判断版本号

### 正则结果

``````
// x.y.z
const versionRegexp = /^(?:\d+\.){2}\d+\$/

console.log(versionRegexp.test('1.1.1'))
console.log(versionRegexp.test('1.000.1'))
console.log(versionRegexp.test('1.000.1.1'))

``````

2.2k 声望
212 粉丝