ECMAScript 4 曾提議讓正則表達式具備 call()/apply() 方法,後來雖未納入標準,但我們可自行擴充 RegExp.prototype 實現類似功能。這不僅便於函數式寫法,也能讓正則在 Array.prototype.filter/map/every/some 等高階函數中直接使用,實現「鴨子類型」多型。
// 讓 RegExp 可像函數一樣被呼叫
RegExp.prototype.call = function (context, str) {
return this.exec(str);
};
RegExp.prototype.apply = function (context, args) {
return this.exec(args[0]);
};
// 兩方法皆忽略 context 參數,傳 null 即可
// 篩選以 "a" 開頭的字串
const result = ["a", "b", "ab", "ba"].filter(/^a/);
// result === ["a", "ab"]
因 exec() 回傳陣列或 null,在布林語境下自動轉為 true/false,完美符合 filter 回調函數簽名。
if (!Array.prototype.filter) {
Array.prototype.filter = function (func, context) {
const results = [];
for (let i = 0; i < this.length; i++) {
if (i in this && func.call(context, this[i], i, this)) {
results.push(this[i]);
}
}
return results;
};
}
if (!Array.prototype.every) {
Array.prototype.every = function (func, context) {
for (let i = 0; i < this.length; i++) {
if (i in this && !func.call(context, this[i], i, this)) return false;
}
return true;
};
}
if (!Array.prototype.some) {
Array.prototype.some = function (func, context) {
for (let i = 0; i < this.length; i++) {
if (i in this && func.call(context, this[i], i, this)) return true;
}
return false;
};
}
if (!Array.prototype.map) {
Array.prototype.map = function (func, context) {
const results = [];
for (let i = 0; i < this.length; i++) {
if (i in this) results[i] = func.call(context, this[i], i, this);
}
return results;
};
}
RegExp.prototype.call?Firefox 早期已實作 Array.prototype.filter,且 exec 間接呼叫可行。但在未實作 call 的瀏覽器(如舊版 IE),filter 內部會嘗試呼叫傳入函數的 call 方法,導致正則表達式報錯。補上 RegExp.prototype.call 即可跨瀏覽器正常運作。
// 從字串陣列提取符合 Email 格式的項目
const emails = ["test@example.com", "invalid", "foo@bar.org", "no-at-sign"];
const valid = emails.filter(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
// valid === ["test@example.com", "foo@bar.org"]
// map 搭配正則提取分組
const urls = ["https://a.com", "https://b.org", "ftp://c.net"];
const domains = urls.map(/https?:\/\/([^\/]+)/);
// domains === ["a.com", "b.org", null] (第三項不匹配回傳 null)
透過幾行原型擴充,正則表達式即化身為可攜帶的謂詞函數,極大提升函數式程式碼的表達力。配合陣列方法 Polyfill,即可在不支援 ES5 的舊環境中優雅地使用現代陣列操作。