String 與 RegExp


如果有個字串,想根據某個字元或字串切割,可以使用 Stringsplit 方法,它會傳回切割後各子字串組成的 Array 實例。例如在 Node.js 的 REPL 中可以如下:

> 'Justin,Monica,Irene'.split(',')
[ 'Justin', 'Monica', 'Irene' ]
> 'JustinOrzMonicaOrzIrene'.split('Orz')
[ 'Justin', 'Monica', 'Irene' ]
> 'Justin\tMonica\tIrene'.split('\t')
[ 'Justin', 'Monica', 'Irene' ]

如果切割字串的依據不單只是某個字元或子字串,而是任意單一數字呢?Stringsplit 方法接受規則表示式:

> 'Justin1Monica2Irene'.split(/\d/)
[ 'Justin', 'Monica', 'Irene' ]

在 JavaScript 中,/regex/ 是規則表示式字面值(Regular expression literal)寫法,實際上這會建立一個〈RegExp 實例〉。

Stringsearch 方法可以使用規則表示式,若找到第一個符合的字串,會傳回索引值,否則傳回 -1:

> 'your right brain has nothing "left" and your left has nothing "right"'.search(/(["'])[^"']*\1/)
29

Stringreplace 方法可以使用規則表示式,例如:

> 'xfooxxxxxxfoo'.replace(/.*?foo/, 'Orz')
'Orzxxxxxxfoo'

嗯?只取代了第一個?如果要全局取代的話,可以使用旗標 g,例如:

> 'xfooxxxxxxfoo'.replace(/.*?foo/g, 'Orz')
'OrzOrz'

JavaScript 中可以使用的旗標有五個:

  • i:忽略大小寫。
  • g:全局匹配。
  • m:允許對多行文字匹配。
  • y:ES6 特性,黏性匹配(sticky match),以 RegExp 實例的 lastIndex 值作為索引,從字串該索引後進行匹配。
  • u:ES6 特性,將 \u{...} 視為 Unicode 碼點來匹配。

ECMAScript 2018(ES9)預計會有個 s 旗標,用來表示 dotAll,也就是 . 將會符合包含換行在內的全部字元,最新版本的 Chrome、Node.js 已經支援。

底下是個黏性匹配的例子:

> const re = /.*?foo/y;
undefined
> re.lastIndex = 4;
4
> 'xfooxxxxxxfoo'.replace(re, 'Orz')
'xfooOrz'

底下是個 Unicode 碼點的例子:

> 'xyz林123'.replace(/\u6797/, 'Lin')
'xyzLin123'
> 'xyz林123'.replace(/\u{6797}/, 'Lin')
'xyz林123'
> 'xyz林123'.replace(/\u{6797}/u, 'Lin')
'xyzLin123'

旗標可以組合,例如想忽略大小寫、全局匹配的話,可以寫成 /regex/ig

可以透過 RegExp 實例的 flagsglobalignoreCasemultilinestickyunicode 等特性,來得知被設定的旗標資訊。

如果規則表示式中有分組設定,在使用 replace 時,可以使用 $num 來捕捉被分組匹配的文字,num 表示第幾個分組,或者是使用 $& 表示整個符合的字串。例如,以下示範如何將使用者郵件位址從 .com 取代為 .cc:

> 'caterpillar@openhome.com'.replace(/(^[a-zA-Z]+\d*)@([a-z]+?.)com/, '$1@$2cc')
'caterpillar@openhome.cc'

replace 的第二個參數也可以是個函式,該函式第一個參數會接受符合的字串,之後的參數會接受分組捕捉到的字串,倒數第二個參數會是符合的字串在原始字串中的偏移值,最後一個參數是原始字串,函式的傳回值會是 replace 的傳回值。

例如,上頭的例子,也可以改用函式:

> 'caterpillar@openhome.com'.replace(/(^[a-zA-Z]+\d*)@([a-z]+?.)com/, (match, g1, g2) => `${g1}@${g2}cc`)
'caterpillar@openhome.cc'

Stringmatch 可以指定規則表示式,如果有符合的字串,它會傳回一個陣列(否則傳回 null),第一個位置(索引 0)是符合的字串,而各分組捕捉到的值,逐一放在後續索引處。

如果分組使用了 (?:...) 就不會捕捉,傳回的陣列中也就不會有該分組的值。

> '0970-666888'.match(/(\d{4})-(\d{6})/);
[ '0970-666888', '0970', '666888', index: 0, input: '0970-666888' ]
> '0970-666888'.match(/(?:\d{4})-(?:\d{6})/);
[ '0970-666888', index: 0, input: '0970-666888' ]
> '0970-666888'.match(/(?:(\d{4})-(\d{6}))/);
[ '0970-666888', '0970', '666888', index: 0, input: '0970-666888' ]

如果加上了全域旗標,那麼 match 傳回的陣列,會是符合表示式的值,不會有分組的部份:

> '0970-666888, 0970-168168'.match(/((\d{4})-(\d{6}))/g);
[ '0970-666888', '0970-168168' ]