RegExp 的 Symbol 協定


在〈String 與 RegExp〉中看過,String 上有 splitsearchreplacematch 方法,在 ES6 之後,RegExp 也定義有這些方法了,不過,必須以〈符號〉來取得:

> RegExp.prototype[Symbol.split]
[Function: [Symbol.split]]
> RegExp.prototype[Symbol.search]
[Function: [Symbol.search]]
> RegExp.prototype[Symbol.replace]
[Function: [Symbol.replace]]
> RegExp.prototype[Symbol.match]
[Function: [Symbol.match]]

這表示,對於底下的這類呼叫字串方法的例子:

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

也可以使用底下的方式:

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

實際上,ES6 在實作上,將 Stringsplitsearchreplacematch 方法,全都委託給傳入的 RegExp 實例上對應的方法,這麼做的原因,說法之一是這麼一來,與規則表示式相關的方法,都可以集中在 RegExp 上了,必要時你也可以繼承 RegExp 來重新定義相關的方法,例如在 MDN 的〈RegExp〉文件中,相關的方法說明中就有一些範例。

實際上,由於 JavaScript 是動態定型語言,這表示在執行 Stringsplitsearchreplacematch 方法時,並不一定要傳入 RegExp 實例,只要是具有 Symbol.splitSymbol.searchSymbol.replaceSymbol.match 協定的物件就可以了。

於是就有了機會,將一些外部的規則表示式程式庫,安插至 String 的規則表示式相關操作中,例如,ECMAScript 2018(ES9)之後才支援命名群組,然而,在安裝了 XRegExp 之後,可以如下支援命名群組:

const XRegExp = require('xregexp');

class Xre {
    constructor(re) {
        this.xRegExp = XRegExp(re);
    }

    [Symbol.replace](str, newSubStr) {
        return XRegExp.replace(str, this.xRegExp, newSubStr);
    }
}

const emailRe = new Xre('(?<user>^[a-zA-Z]+\\d*)@(?<preCom>[a-z]+?.)com');

const replaced = 'caterpillar@openhome.com'.replace(
    emailRe, '${user}@${preCom}cc'
);

console.log(replaced); // 顯示 caterpillar@openhome.cc

這麼一來,JavaScript 的相關引擎實作支援命名群組之後,要將 Xre 相關的部份換成標準實作,修改幅度就會比較低一些…XD