圖解 25 道正則面試題
前言
數字千分位分割、手機號 3-3-4 格式拼接、trim 函數實現、HTML 轉義、獲取 url query 參數... 你是不是也經常在面試和工作中遇到呢?讓我們一起看看,如何用正則將他們一網打盡吧!!!
- 數字價格千分位分割
將 123456789 變成 123,456,789
這道題估計大家在面試和工作中也經常遇到,出現頻率比較高。
正則結果
'123456789'.replace(/(?!^)(?=(\d{3})+$)/g, ',') // 123,456,789
分析過程
題目意思大概是:
-
從後往前
每三個數字
前加一個逗號 -
開頭不能加逗號 (比如:
123
最後不能變成,123
)
是不是很符合 (?=p) 的規律呢?p 可以表示每三個數字,要添加的逗號所處的位置正好是 (?=p) 匹配出來的位置。
「第一步,嘗試先把後面第一個逗號弄出來」
let price = '123456789'
let priceReg = /(?=\d{3}$)/
console.log(price.replace(proceReg, ',')) // 123456,789
「第二步,把所有的逗號都弄出來」
要把所有的逗號都弄出來,主要要解決的問題是怎麼表示三個數字一組
, 也就是 3 的倍數。我們知道正則中括號可以把一個 p 模式變成一個小整體,所以利用括號的特性,可以這樣寫
let price = '123456789'
let priceReg = /(?=(\d{3})+$)/g
console.log(price.replace(priceReg, ',')) // ,123,456,789
「第三步,去掉首位的逗號,」
上面已經基本上實現需求了,但是還不夠,首位還會出現逗號,那怎麼把首位的逗號去除呢?想想是不是有一個知識正好滿足這個場景?沒錯 (?!p),就是他了,兩者結合就是從後往前每三個數字的位置前添加逗號,但是這個位置不能是 ^ 首位。
let price = '123456789'
let priceReg = /(?!^)(?=(\d{3})+$)/g
console.log(price.replace(priceReg, ',')) // 123,456,789
- 手機號 3-4-4 分割
將手機號 18379836654 轉化爲 183-7983-6654
表單蒐集場景,經常遇到的手機格式化
正則結果
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-4-4 分割擴展
將手機號 18379836654 轉化爲 183-7983-6654 需要滿足以下條件
-
123 => 123
-
1234 => 123-4
-
12345 => 123-45
-
123456 => 123-456
-
1234567 => 123-4567
-
12345678 => 123-4567-8
-
123456789 => 123-4567-89
-
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))
分析過程
這裏用 (?=p) 就不太合適了,例如 1234 就會變成 - 1234。我們需要另尋他法, 正則中還有其他的知識點方便處理這種場景嗎?有 (?<=p)
「第一步, 將第一個 - 弄出來」
const formatMobile = (mobile) => {
return String(mobile).replace(/(?<=\d{3})\d+/, '-')
}
console.log(formatMobile(123)) // 123
console.log(formatMobile(1234)) // 123-4
「將第二個 - 弄出來」
接着我們弄出來第二個,第二個 - 正好處於第 8 位 (1234567-) 的位置。
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。驗證密碼的合法性
密碼長度是 6-12 位,由數字、小寫字母和大寫字母組成,但必須至少包括 2 種字符
正則結果
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
分析過程
題目由三個條件組成
-
密碼長度是 6-12 位
-
由數字、小寫字符和大寫字母組成
-
必須至少包括 2 種字符
「第一步,寫出條件 1 和 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
// 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
- 提取連續重複的字符
將有重複的字符提取出來,例如 12323454545666,提取 ['23', '45', '6']
正則結果
const collectRepeatStr = (str) => {
let repeatStrs = []
const repeatRe = /(.+)\1+/g
str.replace(repeatRe, ($0, $1) => {
$1 && repeatStrs.push($1)
})
return repeatStrs
}
分析過程
題目中有幾個關鍵信息是
-
連續重複的字符
-
連續重複的字符數的長度是不限的(如 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')) // true
「第二步,寫出表示有 n 個字符重複的正則」
因爲並不確定是要匹配 11 還是45
45
所以括號內需要用量詞 + 來體現 n 個重複字符,而反向引用本身也可以是大於一個的,例如 45
45
45
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"]
- 實現一個 trim 函數
去除字符串的首尾空格
正則結果
// 去除空格法
const trim = (str) => {
return str.replace(/^\s*|\s*$/g, '')
}
// 提取非空格法
const trim = (str) => {
return str.replace(/^\s*(.*?)\s*$/g, '$1')
}
分析過程
初看題目我們腦海中閃過的做法是把空格部分刪除掉,保留非空格的部分
,但是也可以換一種思路,也可以把非空格的部分提取出來,不管空格的部分。接下來我們來寫一下兩種 trim 方法的實現
「方式一、去除空格法」
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(' 前端 胖頭魚 ')) // 前端 胖頭魚
- HTML 轉義
防止 XSS 攻擊的方式之一就是做 HTML 轉義,轉義規則如下,要求將對應字符轉換成等值的實體。而反轉義則是將轉義後的實體轉換爲對應的字符
正則結果
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>
`))
/*
<div>
<p>hello world</p>
</div>
*/
- 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(`
<div>
<p>hello world</p>
</div>
`))
/*
<div>
<p>hello world</p>
</div>
*/
- 將字符串駝峯化
如下規則,將對應字符串變成駝峯寫法
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() : ''
})
}
分析過程
分析題目的規律
-
每個單詞的前面都有**「0 個或者多個」**
-
空格
_
如 (Foo
、--foo
、__FOO
、_BAR
、Bar
) -
-
空格
_
後面有可能不跟任何東西 如 (__
、--
)
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
- 將字符串首字母轉化爲大寫,剩下爲小寫
例如 hello world 轉爲爲 Hello World
正則結果
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
- 獲取網頁中所有 img 標籤的圖片地址
要求必須是在線鏈接 例如
https://xxx.juejin.com/a.jpg
、http://xxx.juejin.com/a.jpg
、//xxx.juejjin.com/a.jpg
分析過程
平時寫過一些爬蟲的同學對匹配 img 標籤的 url 一定不陌生,爲了準確抓取小姐姐的圖片地址,一定動用了你各種聰明才智,最後也如願以償。
題目中限定了
-
圖片標籤
img
-
需要是在線鏈接形式,一些 base64 的圖片需要過濾掉
接下來我們直接看結果,通過可視化的形式看一下這個正則要表示的意思是啥
const matchImgs = (sHtml) => {
const imgUrlRegex = /<img[^>]+src="((?:https?:)?\/\/[^"]+)"[^>]*?>/gi
let matchImgUrls = []
sHtml.replace(imgUrlRegex, (match, $1) => {
$1 && matchImgUrls.push($1)
})
return matchImgUrls
}
我們把正則分成幾個部分來看
-
img 標籤到 src 之間的部分,只要不是 >,其他的啥都可以
-
括號內的部分,也就是我們要提取的 url 部分,作爲一個捕獲分組存在,方便直接獲取
2.1 (?:https?:)? 表示支持協議頭爲 http: 或者 https:
2.2 括號外面的?,表示可以沒有協議頭,即支持
//xxx.juejjin.com/a.jpg
形式的鏈接2.3 接着是兩個斜線
2.4 因爲 src="" 雙引號內的部分即爲鏈接,所以
[^"]+
表示除了 " 其他部分都行 -
接着就是 " 到 img 結束標籤 > 之間的部分了,除了 > 之外,啥都可以
[^>]*?
「試試結果」
我們到知乎,打開控制檯,可以看到是符合預期的。
- 通過 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=前端胖頭魚
所處的位置可能是
-
緊跟着問號
?name = 前端胖頭魚 & sex=boy -
在最後的位置
?sex=boy&name = 前端胖頭魚 -
在1和2之間
?sex=boy&name = 前端胖頭魚 & age=100
所以只要處理三個地方基本就可以通過正則來取了
-
name 前面只能是? 或者 &
-
value 的值可以除了是 & 以爲的任意東西
-
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')) // 前端胖頭魚
- 匹配 24 小時制時間
判斷時間 time 是否符合 24 小時制 要求可以匹配規則如下
-
01:14
-
1:14
-
1:1
-
23:59
正則結果
const check24TimeRegexp = /^(?:(?:0?|1)\d|2[0-3]):(?:0?|[1-5])\d$/
分析過程
24 小時制的時間的時
和分
分別需要滿足
「時」
-
第一位可以是 012
-
第二位
2.1 當第一位是 01 時,第二位可以是任意數字
2.2 當第二位是 2 時,第二位只能是 0、1、2、3
「分」
-
第一位可以是 0、1、2、3、4、5
-
第二位可以是任意數字
「第一步,先寫出符合 1 和 4 規則的正則」
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
- 匹配日期格式
要求匹配 (yyyy-mm-dd、yyyy.mm.dd、yyyy/mm/dd),例如
2021-08-22
、2021.08.22
、2021/08/22
可以不考慮平閏年
正則結果
const checkDateRegexp = /^\d{4}([-\.\/])(?:0[1-9]|1[0-2])\1(?:0[1-9]|[12]\d|3[01])$/
分析過程
「日期格式主要分爲三個部分」
-
yyyy年部分
這部分只要是四個數字就可以\d{4}
-
mm月份部分
2.1 一年只有 12 個月,前 10 個月可以用
0\d
2.2 10 月份及其以後以後
1[0-2]
-
dd日部分
3.1 一個月最多是 31 日
3.2 最小是 1 號
「分隔符」
需要注意的是分割符必須一樣不能 -./ 三種混用,比如2021.08-22
根據以上分析我們可以寫出
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
可視化形式中有一個 Backref #1 ,也就是反向引用第一個分組也就 ([-\.\/])
、這樣就保證了分割符一定是一樣的
- 匹配 16 進制的顏色值
要求從字符串 string 中匹配類似
#ffbbad
、#FFF
16 進制顏色值
正則結果
const matchColorRegex = /#(?:[\da-zA-Z]{6}|[\da-zA-Z]{3})/g
分析過程
16 進制的顏色值由以下兩部分組成
-
#
-
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' ]
我們不能把正則寫成/#(?:[\da-zA-Z]{3}|[\da-zA-Z]{6})/g
, 因爲正則中的多選分支 | 是惰性匹配的,優先匹配前面的分支,這時候去匹配'#12f3a1 #ffBabd #FFF #123 #586'
, 將會得到[ '#12f', '#ffB', '#FFF', '#123', '#586' ]
- 檢測 URL 前綴
檢查一個 url 是否是 http 或者 https 協議頭
這個相對簡單,但是在日常工作中還是經常碰到。
正則結果
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
- 檢測中文
檢測字符串 str 是否是都由中文組成
最重要是要確定中文在 unicode 的編碼範圍漢字 Unicode 編碼範圍,如果要加上基本漢字之外的匹配,只需要用多選分支即可
分析過程
const checkChineseRegex = /^[\u4E00-\u9FA5]+$/
console.log(checkChineseRegex.test('前端胖頭魚'))
console.log(checkChineseRegex.test('1前端胖頭魚'))
console.log(checkChineseRegex.test('前端胖頭魚2'))
- 匹配手機號
檢測一個字符串是否符合手機號的規則
「時效性」
手機號本身是有時效性的,各大運營商有時候會推出新的號碼,所以我們的正則也具有時效性,需要及時補充
「規律性」
具體規律可以查看 中國大陸移動終端通信號碼
解析過程
正則參考自 ChinaMobilePhoneNumberRegex
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'))
當遇到一個很長看起來很複雜的正則的時候,有什麼好辦法可以讓我們看懂它?
「可以藉助可視化工具輔助我們拆解正則。」
所以 mobileRegex 可以分成以下幾個部分
-
(?:\+?86)?
: 手機前綴,括號內通過?:
標識非引用分組 -
1: 所有的手機號都是以 1 開頭
-
(a|b|c|...): 2~5 位的各種情況,通過多選分支 | 進行逐一解釋
-
\d{6}: 6 位任意數字
拆解開來後會發現其實也不復雜,只是第三部分因爲可能性太多了,用了好多多選分支來說明,只要理清楚了手機號規則,每個分組裏面的規律也就不難了。
- 英文單詞加前後空格
字母漢字組成的字符串,用正則給英文單詞加前後空格。如:
you說來是come,去是go
=>you 說來是 come ,去是 go
例子
解析過程
這裏只要瞭解正則中\b
位置的概念就可以了,\b
的意思是單詞的邊界, 具體講有三點規則
-
\w 和 \ W 之間的位置
-
^ 與 \ w 之間的位置
-
\w 與 $ 之間的位置
所以:
第一個單詞you
,符合規則 2、
第二個單詞 come,符合規則 1、
第三個單詞符合 go,符合規則 3
const wordRegex = /\b/g
console.log('you說來是come,去是go'.replace(/\b/g, ' ')) // ` you 說來是 come ,去是 go `
- 字符串大小寫取反
將字符串大小寫取反,例如 hello WORLD => HELLO world
解析過程
這題比較容易想到的是通過 ASCII 碼確定大小寫,然後再轉成對應的值即可,但是既然是正則的總結,我們就嘗試一下通過正則來完成。
不通過 ASCII 碼那如何確定一個字符是否是大寫呢?其實只要將他變成了大寫字符,再與元字符比較一下,相等那說明遠字符也是大寫的。比如
對於字符串 x = `A`
'A'.toUpperCase()得到的y是A
y === x
那麼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
- windows 下的
文件
夾和文件路徑
要求匹配如下路徑
-
C:\Documents\Newsletters\Summer2018.pdf
-
C:\Documents\Newsletters\
-
C:\Documents\Newsletters
-
C:\
正則結果
const windowsPathRegex = /^[a-zA-Z]:\\(?:[^\\:*<>|"?\r\n/]+\\?)*(?:(?:[^\\:*<>|"?\r\n/]+)\.\w+)?$/;
解析過程
windows 下的文件規則大概由這幾部分構成
磁盤符:\文件夾\文件夾\文件
-
磁盤符:只能是英文構成
[a-zA_Z]:\\
-
文件夾名字:不包含一些特殊符號且可出現任意次, 最後的 \ 可以沒有
([^\\:*<>|"?\r\n/]+\\?)*
-
文件名字:
([^\\:*<>|"?\r\n/]+)\.\w+
,但是文件可以沒有
const windowsPathRegex = /^[a-zA-Z]:\\(?:[^\\:*<>|"?\r\n/]+\\?)*(?:(?:[^\\:*<>|"?\r\n/]+)\.\w+)?$/;
console.log( windowsPathRegex.test("C:\\Documents\\Newsletters\\Summer2018.pdf") ); // true
console.log( windowsPathRegex.test("C:\\Documents\Newsletters\\") ); // true
console.log( windowsPathRegex.test("C:\\Documents\Newsletters") ); // true
console.log( windowsPathRegex.test("C:\\") ); // true
- 匹配 id(寫爬蟲獲取 html 經常用到)
要求
<div>hello world</div>
中的 id box
正則結果
const matchIdRegexp = /id="([^"]*)"/
console.log(`
<div id="box">
hello world
</div>
`.match(matchIdRegexp)[1])
解析過程
寫爬蟲的過程中經常需要匹配指定條件的 dom 元素,然後再去做對應的操作。那麼怎麼獲取 box 呢
<div id="box">
hello world
</div>
相信大家最先想到的是這個正則id="(.*)"
const matchIdRegexp = /id="(.*)"/
console.log(`
<div id="box">
hello world
</div>
`.match(matchIdRegexp)[1])
但是id="(.*)"
很容易導致回溯,從而耗費更多的匹配時間。有什麼優化的方式嗎?
是的只需要將.
換成[^"]
即可,當遇到 " 時,正則即認爲匹配結束,也就不會發生回溯了。
const matchIdRegexp = /id="([^"]*)"/
console.log(`
<div id="box">
hello world
</div>
`.match(matchIdRegexp)[1])
- 匹配 id 擴展(獲取掘金首頁 html 所有 id)
我們試試能不能批量獲取 id
正則結果
const idRegexp = /id="([^"]+)"/g
document.body.innerHTML
.match(idRegexp)
.map((idStr) => idStr.replace(idRegexp, '$1'))
- 大於等於 0, 小於等於 150, 支持小數位出現 5, 如 145.5, 用於判斷考卷分數
正則結果
const pointRegex = /^(?:[1-9]?\d|1[0-4]\d)$/
分析過程
我們可以將這道題分成兩部分看
-
整數部分
-
個位整數
-
十位整數
-
百位整數但小於 150
-
小數部分:只能是
.5
或者沒有
「先嚐試寫整數部分」
// 1. 如何表示個位數? /\d/
// 2. 如何表示十位數? /[1-9]\d/
// 3. 個位和十位如何一起表示? /[1-9]?\d/
// 4. 小於150的百位數呢? /1[0-4]\d/
// 所以結合起來整數部分可以用以下正則表示
const pointRegex = /^(?:[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)) // false
「再加上小數部分」
// 小數部分相對簡單 /(?:\.5)?/,所以整體結合起來就是
const pointRegex = /^(?:[1-9]?\d|1[0-4]\d)(?:\.5)?$/
console.log(pointRegex.test(0))
console.log(pointRegex.test(10))
console.log(pointRegex.test(100))
console.log(pointRegex.test(110.5))
console.log(pointRegex.test(150))
- 判斷版本號
要求版本號必須是 X.Y.Z 格式,其中 XYZ 都是至少一位的數字
正則結果
// 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'))
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/RYb2Jlse8tbwUuINbRDlmw