exec() 方法用於檢索字符串中的正則表達式的匹配。
1、exec() 方法返回一個數組,其中存放匹配的結果,如果未找到匹配,則返回值為 null。
let str = "aaa"; let r1 = /a/g; let r2 = /b/g; console.log("r1匹配結果:", r1.exec(str)); console.log("r2匹配結果:", r2.exec(str));

exec() 方法的功能非常強大,它是一個通用的方法,而且使用起來也比 test() 方法以及支持正則表達式的 String 對象的方法更為復雜。
除了數組元素和 length 屬性之外,exec() 方法還返回兩個屬性。index 屬性聲明的是匹配文本的第一個字符的位置。input 屬性則存放的是被檢索的字符串 string。
我們可以看得出,在調用非全局(上面的例子是全局)的 RegExp 對象的 exec() 方法時,返回的數組與調用方法 String.match() 返回的數組是相同的。
let str = "aaa"; let r1 = /a/g; let r2 = /a/; console.log("r1匹配結果:", r1.exec(str)); console.log("str-r1匹配結果:", str.match(r1)); console.log("r2匹配結果:", r2.exec(str)); console.log("str-r2匹配結果:", str.match(r2));

2、此數組的第 0 個元素是與正則表達式相匹配的文本,第 1 個元素是與 RegExpObject 的第 1 個子表達式相匹配的文本(如果有的話),第 2 個元素是與 RegExpObject 的第 2 個子表達式相匹配的文本(如果有的話),以此類推。
let s = "5aabcaba_a4aba_a a_a_abca_a a a_acbbaa b aa"; let r = /(((a)b)c)|(ba)/g; console.log(r.exec(s));

上面代碼的正則共有4個子表達式,按順序分別為:(((a)b)c)、((a)b)、(a)、(ba)
所以上面數組的第0個元素放的是:與正則表達式相匹配的文本,正則為(((a)b)c)|(ba),元素值為abc
第1個元素放的是第1個子表達式的(在第0個元素的基礎上)匹配的值,即abc
第2個元素放的是第2個子表達式匹配的值,即ab
第3個元素放的是第3個子表達式匹配的值,即a
第4個元素放的是第4個子表達式匹配的值,由於第4個子表達式沒有匹配到值,所以為undefined
ES2018 引入了具名組匹配(Named Capture Groups),允許為每一個組匹配指定一個名字,既便於閱讀代碼,又便於引用。
let s = "5aabcaba_a4aba_a a_a_abca_a a a_acbbaa b aa"; let r = /(?<first>(?<second>(?<third>a)b)c)|(?<fourth>ba)/g; let re = r.exec(s); console.log(re); console.log("re.groups.first--", re.groups.first); console.log("re.groups.second--", re.groups.second); console.log("re.groups.third--", re.groups.third); console.log("re.groups.fourth--", re.groups.fourth);

如果具名組沒有匹配,那么對應的groups對象屬性會是undefined。
const RE_OPT_A = /^(?<as>a+)?$/; const matchObj = RE_OPT_A.exec(''); matchObj.groups.as // undefined 'as' in matchObj.groups // true
上面代碼中,具名組as沒有找到匹配,那么matchObj.groups.as屬性值就是undefined,並且as這個鍵名在groups是始終存在的。
3、解構賦值和替換
有了具名組匹配以后,可以使用解構賦值直接從匹配結果上為變量賦值。
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one // foo
two // bar
字符串替換時,使用$<組名>引用具名組。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'
上面代碼中,replace方法的第二個參數是一個字符串,而不是正則表達式。
replace方法的第二個參數也可以是函數,該函數的參數序列如下。
'2015-01-02'.replace(re, ( matched, // 整個匹配結果 2015-01-02 capture1, // 第一個組匹配 2015 capture2, // 第二個組匹配 01 capture3, // 第三個組匹配 02 position, // 匹配開始的位置 0 S, // 原字符串 2015-01-02 groups // 具名組構成的一個對象 {year, month, day} ) => { let {day, month, year} = groups; return `${day}/${month}/${year}`; });
具名組匹配在原來的基礎上,新增了最后一個函數參數:具名組構成的一個對象。函數內部可以直接對這個對象進行解構賦值。
4、但是,當 RegExpObject 是一個全局正則表達式時,exec() 的行為就稍微復雜一些。它會在 RegExpObject 的 lastIndex 屬性指定的字符處開始檢索字符串 string。當 exec() 找到了與表達式相匹配的文本時,在匹配后,它將把 RegExpObject 的 lastIndex 屬性設置為匹配文本的最后一個字符的下一個位置。這就是說,您可以通過反復調用 exec() 方法來遍歷字符串中的所有匹配文本。當 exec() 再也找不到匹配的文本時,它將返回 null,並把 lastIndex 屬性重置為 0。
let s = "5aabcaba_a4aba_a a_a_abca_a a a_acbbaa b aa"; let r = /(((a)b)c)|(ba)/g; console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s));

由上圖可知,當第6個console.log(r.exec(s));沒有匹配到,所以返回了null,lastIndex 也會重置為0,下一次調用時,又從頭開始匹配了。
注意:如果在一個字符串中完成了一次模式匹配之后要開始檢索新的字符串,就必須手動地把 lastIndex 屬性重置為 0。
let s = "5aabcaba_a4aba_a a_a_abca_a a a_acbbaa b aa"; let r = /(((a)b)c)|(ba)/g; console.log(r.exec(s)); console.log("把 lastIndex 屬性重置為 0"); r.lastIndex = 0; console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s)); console.log(r.exec(s));

提示:請注意,無論 RegExpObject 是否是全局模式,exec() 都會把完整的細節添加到它返回的數組中。這就是 exec() 與 String.match() 的不同之處,后者在全局模式下返回的信息要少得多。因此我們可以這么說,在循環中反復地調用 exec() 方法是唯一一種獲得全局模式的完整模式匹配信息的方法。
