JavaScript 字串與規則表示式
July 11, 2022如果有個字串,想根據某個字元或字串切割,可以使用 String
的 split
方法,它會傳回切割後各子字串組成的 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' ]
規則表示式字面值
如果切割字串的依據不單只是某個字元或子字串,而是任意單一數字呢?String
的 split
方法接受規則表示式:
> 'Justin1Monica2Irene'.split(/\d/)
[ 'Justin', 'Monica', 'Irene' ]
在 JavaScript 中,/regex/
是規則表示式字面值(Regular expression literal)寫法,實際上這會建立一個 RegExp
實例。
String
的 search
方法可以使用規則表示式,若找到第一個符合的字串,會傳回索引值,否則傳回 -1:
> 'your right brain has nothing "left" and your left has nothing "right"'.search(/(["'])[^"']*\1/)
29
String
的 replace
方法可以使用規則表示式,例如:
> '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
,也就是 .
將會符合包含換行在內的全部字元。
底下是個黏性匹配的例子:
> 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
實例的 flags
、global
、ignoreCase
、multiline
、sticky
、unicode
等特性,來得知被設定的旗標資訊。
分組設定
如果規則表示式中有分組設定,在使用 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'
String
的 match
可以指定規則表示式,如果有符合的字串,它會傳回一個陣列(否則傳回 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' ]