圖解 25 道正則面試題

前言

數字千分位分割、手機號 3-3-4 格式拼接、trim 函數實現、HTML 轉義、獲取 url query 參數... 你是不是也經常在面試和工作中遇到呢?讓我們一起看看,如何用正則將他們一網打盡吧!!!

  1. 數字價格千分位分割

將 123456789 變成 123,456,789

這道題估計大家在面試和工作中也經常遇到,出現頻率比較高。

正則結果

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

分析過程

題目意思大概是:

  1. 從後往前每三個數字前加一個逗號

  2. 開頭不能加逗號 (比如: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
  1. 手機號 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
  1. 手機號 3-4-4 分割擴展

將手機號 18379836654 轉化爲 183-7983-6654 需要滿足以下條件

  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))

分析過程

這裏用 (?=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

分析過程

題目由三個條件組成

  1. 密碼長度是 6-12 位

  2. 由數字、小寫字符和大寫字母組成

  3. 必須至少包括 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. 數字和大寫字母組合

  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
  1. 提取連續重複的字符

將有重複的字符提取出來,例如 12323454545666,提取 ['23', '45', '6']

正則結果

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')) // 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"]
  1. 實現一個 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('  前端 胖頭魚  ')) // 前端 胖頭魚
  1. HTML 轉義

防止 XSS 攻擊的方式之一就是做 HTML 轉義,轉義規則如下,要求將對應字符轉換成等值的實體。而反轉義則是將轉義後的實體轉換爲對應的字符

ry81cH

正則結果

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>

*/
  1. 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. 將字符串駝峯化

如下規則,將對應字符串變成駝峯寫法

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_BARBar)

  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
  1. 將字符串首字母轉化爲大寫,剩下爲小寫

例如 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
  1. 獲取網頁中所有 img 標籤的圖片地址

要求必須是在線鏈接 例如 https://xxx.juejin.com/a.jpghttp://xxx.juejin.com/a.jpg//xxx.juejjin.com/a.jpg

分析過程

平時寫過一些爬蟲的同學對匹配 img 標籤的 url 一定不陌生,爲了準確抓取小姐姐的圖片地址,一定動用了你各種聰明才智,最後也如願以償。

題目中限定了

  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 結束標籤 > 之間的部分了,除了 > 之外,啥都可以 [^>]*?

「試試結果」

我們到知乎,打開控制檯,可以看到是符合預期的。

  1. 通過 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')) // 前端胖頭魚
  1. 匹配 24 小時制時間

判斷時間 time 是否符合 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. 第二位可以是任意數字

「第一步,先寫出符合 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
  1. 匹配日期格式

要求匹配 (yyyy-mm-dd、yyyy.mm.dd、yyyy/mm/dd),例如2021-08-222021.08.222021/08/22 可以不考慮平閏年

正則結果

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 號

「分隔符」

需要注意的是分割符必須一樣不能 -./ 三種混用,比如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 ,也就是反向引用第一個分組也就 ([-\.\/])、這樣就保證了分割符一定是一樣的

  1. 匹配 16 進制的顏色值

要求從字符串 string 中匹配類似 #ffbbad#FFF16 進制顏色值

正則結果

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' ]

我們不能把正則寫成/#(?:[\da-zA-Z]{3}|[\da-zA-Z]{6})/g, 因爲正則中的多選分支 | 是惰性匹配的,優先匹配前面的分支,這時候去匹配'#12f3a1 #ffBabd #FFF #123 #586', 將會得到[ '#12f', '#ffB', '#FFF', '#123', '#586' ]

  1. 檢測 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

  1. 檢測中文

檢測字符串 str 是否是都由中文組成

最重要是要確定中文在 unicode 的編碼範圍漢字 Unicode 編碼範圍,如果要加上基本漢字之外的匹配,只需要用多選分支即可

分析過程

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

console.log(checkChineseRegex.test('前端胖頭魚'))
console.log(checkChineseRegex.test('1前端胖頭魚'))
console.log(checkChineseRegex.test('前端胖頭魚2'))

  1. 匹配手機號

檢測一個字符串是否符合手機號的規則

「時效性」

手機號本身是有時效性的,各大運營商有時候會推出新的號碼,所以我們的正則也具有時效性,需要及時補充

「規律性」

具體規律可以查看 中國大陸移動終端通信號碼

解析過程

正則參考自 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 可以分成以下幾個部分

  1. (?:\+?86)?: 手機前綴,括號內通過?:標識非引用分組

  2. 1: 所有的手機號都是以 1 開頭

  3. (a|b|c|...): 2~5 位的各種情況,通過多選分支 | 進行逐一解釋

  4. \d{6}: 6 位任意數字

拆解開來後會發現其實也不復雜,只是第三部分因爲可能性太多了,用了好多多選分支來說明,只要理清楚了手機號規則,每個分組裏面的規律也就不難了。

  1. 英文單詞加前後空格

字母漢字組成的字符串,用正則給英文單詞加前後空格。如:you說來是come,去是go => you 說來是 come ,去是 go 例子

解析過程

這裏只要瞭解正則中\b位置的概念就可以了,\b的意思是單詞的邊界, 具體講有三點規則

  1. \w 和 \ W 之間的位置

  2. ^ 與 \ w 之間的位置

  3. \w 與 $ 之間的位置

所以:

第一個單詞you,符合規則 2、

第二個單詞 come,符合規則 1、

第三個單詞符合 go,符合規則 3

const wordRegex = /\b/g

console.log('you說來是come,去是go'.replace(/\b/g, ' ')) // ` you 說來是 come ,去是 go `
  1. 字符串大小寫取反

將字符串大小寫取反,例如 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
  1. windows 下的文件夾和文件路徑

要求匹配如下路徑

  1. C:\Documents\Newsletters\Summer2018.pdf

  2. C:\Documents\Newsletters\

  3. C:\Documents\Newsletters

  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:\\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

  1. 匹配 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])
  1. 匹配 id 擴展(獲取掘金首頁 html 所有 id)

我們試試能不能批量獲取 id

正則結果

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

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

  1. 大於等於 0, 小於等於 150, 支持小數位出現 5, 如 145.5, 用於判斷考卷分數

正則結果

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

分析過程

我們可以將這道題分成兩部分看

  1. 整數部分

  2. 個位整數

  3. 十位整數

  4. 百位整數但小於 150

  5. 小數部分:只能是.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))
  1. 判斷版本號

要求版本號必須是 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